diff --git a/CHANGELOG.md b/CHANGELOG.md index d8f912c41ed..3e4576736b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ## 4.31.0 (staging) +- added: "Change Username" setting - added: Support for Zano alias name resolution. - changed: Don't allow multiple pending EVM transactions. diff --git a/src/__tests__/scenes/__snapshots__/SettingsScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/SettingsScene.test.tsx.snap index 28069fb8953..2fe527519c7 100644 --- a/src/__tests__/scenes/__snapshots__/SettingsScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/SettingsScene.test.tsx.snap @@ -807,6 +807,153 @@ exports[`SettingsScene should render SettingsScene 1`] = ` ] } /> + + + Change Username + + + + + + + +  + + + + + > { +): ThunkAction> { return async (dispatch, getState) => { const { message, @@ -64,6 +64,6 @@ export function validatePassword( /> )) - return password != null + return password } } diff --git a/src/actions/SettingsActions.tsx b/src/actions/SettingsActions.tsx index c77c0e2bc2b..36993cebd0a 100644 --- a/src/actions/SettingsActions.tsx +++ b/src/actions/SettingsActions.tsx @@ -288,16 +288,18 @@ export async function showReEnableOtpModal( } } -export function showUnlockSettingsModal(): ThunkAction> { +export function showUnlockSettingsModal(): ThunkAction< + Promise +> { return async (dispatch, getState) => { - const passwordValid = await dispatch(validatePassword()) - if (passwordValid) { + const password = await dispatch(validatePassword()) + if (password != null) { dispatch({ type: 'UI/SETTINGS/SET_SETTINGS_LOCK', data: false }) } - return passwordValid + return password } } diff --git a/src/actions/WalletListMenuActions.tsx b/src/actions/WalletListMenuActions.tsx index e331419e95f..7781220edd8 100644 --- a/src/actions/WalletListMenuActions.tsx +++ b/src/actions/WalletListMenuActions.tsx @@ -277,13 +277,14 @@ export function walletListMenuAction( // Show the scam warning modal if needed await showScamWarningModal('firstPrivateKeyView') - const passwordValid = await dispatch( - validatePassword({ - title: lstrings.fragment_wallets_get_seed_title, - submitLabel: lstrings.fragment_wallets_get_seed_wallet, - warningMessage: lstrings.fragment_wallets_get_seed_warning_message - }) - ) + const passwordValid = + (await dispatch( + validatePassword({ + title: lstrings.fragment_wallets_get_seed_title, + submitLabel: lstrings.fragment_wallets_get_seed_wallet, + warningMessage: lstrings.fragment_wallets_get_seed_warning_message + }) + )) != null if (passwordValid) { const { name, id, type } = wallet @@ -322,14 +323,15 @@ export function walletListMenuAction( case 'getRawKeys': { return async (dispatch, getState) => { - const passwordValid = await dispatch( - validatePassword({ - title: lstrings.fragment_wallets_get_raw_keys_title, - warningMessage: - lstrings.fragment_wallets_get_raw_keys_warning_message, - submitLabel: lstrings.string_get_raw_keys - }) - ) + const passwordValid = + (await dispatch( + validatePassword({ + title: lstrings.fragment_wallets_get_raw_keys_title, + warningMessage: + lstrings.fragment_wallets_get_raw_keys_warning_message, + submitLabel: lstrings.string_get_raw_keys + }) + )) != null if (passwordValid) { const state = getState() const { account } = state.core diff --git a/src/components/Main.tsx b/src/components/Main.tsx index 4940dfe9495..2802bc16731 100644 --- a/src/components/Main.tsx +++ b/src/components/Main.tsx @@ -57,6 +57,7 @@ import { AssetSettingsScene as AssetSettingsSceneComponent } from './scenes/Asse import { ChangeMiningFeeScene as ChangeMiningFeeSceneComponent } from './scenes/ChangeMiningFeeScene' import { ChangePasswordScene as ChangePasswordSceneComponent } from './scenes/ChangePasswordScene' import { ChangePinScene as ChangePinSceneComponent } from './scenes/ChangePinScene' +import { ChangeUsernameScene as ChangeUsernameSceneComponent } from './scenes/ChangeUsernameScene' import { CoinRankingDetailsScene as CoinRankingDetailsSceneComponent } from './scenes/CoinRankingDetailsScene' import { CoinRankingScene as CoinRankingSceneComponent } from './scenes/CoinRankingScene' import { ConfirmScene as ConfirmSceneComponent } from './scenes/ConfirmScene' @@ -164,6 +165,7 @@ const BuyScene = ifLoggedIn(BuySceneComponent) const ChangeMiningFeeScene = ifLoggedIn(ChangeMiningFeeSceneComponent) const ChangePasswordScene = ifLoggedIn(ChangePasswordSceneComponent) const ChangePinScene = ifLoggedIn(ChangePinSceneComponent) +const ChangeUsernameScene = ifLoggedIn(ChangeUsernameSceneComponent) const DuressPinScene = ifLoggedIn(DuressPinSceneComponent) const ChangeRecoveryScene = ifLoggedIn(ChangeRecoverySceneComponent) const CoinRankingDetailsScene = ifLoggedIn(CoinRankingDetailsSceneComponent) @@ -625,6 +627,14 @@ const EdgeAppStack = () => { headerRight: () => null }} /> + null + }} + /> {} + +export const ChangeUsernameScene = (props: Props) => { + const { navigation, route } = props + const { password } = route.params + const dispatch = useDispatch() + + const account = useSelector(state => state.core.account) + const context = useSelector(state => state.core.context) + + const handleComplete = useHandler(() => { + navigation.goBack() + }) + + const handleLogEvent = useHandler((event, values) => { + dispatch(logEvent(event, values)) + }) + + return ( + + + + ) +} diff --git a/src/components/scenes/SettingsScene.tsx b/src/components/scenes/SettingsScene.tsx index 3afd382aa43..bd3cff963eb 100644 --- a/src/components/scenes/SettingsScene.tsx +++ b/src/components/scenes/SettingsScene.tsx @@ -91,7 +91,6 @@ export const SettingsScene = (props: Props) => { const [localContactPermissionOn, setLocalContactsPermissionOn] = React.useState(false) - const [isDarkTheme, setIsDarkTheme] = React.useState( theme === config.darkTheme ) @@ -106,6 +105,7 @@ export const SettingsScene = (props: Props) => { const [touchIdText, setTouchIdText] = React.useState( lstrings.settings_button_use_touchID ) + const [validatedPassword, setValidatedPassword] = React.useState() const iconSize = theme.rem(1.25) const isLightAccount = username == null @@ -127,25 +127,31 @@ export const SettingsScene = (props: Props) => { showBackupModal({ navigation: navigation as NavigationBase }) }) - const handleUnlock = useHandler(() => { + const handleLockUnlock = useHandler(async () => { if (!isLocked) { dispatch({ type: 'UI/SETTINGS/SET_SETTINGS_LOCK', data: true }) + setValidatedPassword(undefined) } else { - handleShowUnlockSettingsModal().catch(err => showError(err)) + const password = await handleShowUnlockSettingsModal().catch(err => { + showError(err) + return undefined + }) + setValidatedPassword(password) } }) /** Returns true if the settings are locked. Otherwise false if they're unlocked. */ const hasLock = async (): Promise => { if (isLocked) { - const passwordValid = await handleShowUnlockSettingsModal().catch(err => { + const password = await handleShowUnlockSettingsModal().catch(err => { showError(err) - return false + return undefined }) - if (!passwordValid) return true + if (password == null) return true + setValidatedPassword(password) dispatch({ type: 'UI/SETTINGS/SET_SETTINGS_LOCK', data: false @@ -225,6 +231,14 @@ export const SettingsScene = (props: Props) => { navigation.navigate('changePin') }) + const handleChangeUsername = useHandler(async (): Promise => { + // Check if settings are locked + if ((await hasLock()) || validatedPassword == null) return + + // Navigate to change username with the validated password + navigation.navigate('changeUsername', { password: validatedPassword }) + }) + const handleChangeOtp = useHandler(async (): Promise => { if (await hasLock()) return navigation.navigate('otpSetup') @@ -421,7 +435,7 @@ export const SettingsScene = (props: Props) => { ? lstrings.settings_button_unlock_settings : lstrings.settings_button_lock_settings } - onPress={handleUnlock} + onPress={handleLockUnlock} /> { label={lstrings.settings_button_pin} onPress={handleChangePin} /> +