Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Region settings #1197

Merged
merged 6 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tall-foxes-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@alephium/mobile-wallet": patch
---

Format amounts based on user's locale
5 changes: 5 additions & 0 deletions .changeset/wild-jobs-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@alephium/mobile-wallet": patch
---

Tap to reveal hidden amounts
2 changes: 1 addition & 1 deletion apps/desktop-wallet/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import useTrackUserSettings from '@/features/analytics/useTrackUserSettings'
import AutoUpdateSnackbar from '@/features/autoUpdate/AutoUpdateSnackbar'
import { languageOptions } from '@/features/localization/languages'
import { systemLanguageMatchFailed, systemLanguageMatchSucceeded } from '@/features/localization/localizationActions'
import useRegionOptions from '@/features/settings/regionSettings/useRegionOptions'
import {
localStorageGeneralSettingsMigrated,
systemRegionMatchFailed,
systemRegionMatchSucceeded
} from '@/features/settings/settingsActions'
import useRegionOptions from '@/features/settings/useRegionOptions'
import { darkTheme, lightTheme } from '@/features/theme/themes'
import { WalletConnectContextProvider } from '@/features/walletConnect/walletConnectContext'
import { useAppDispatch, useAppSelector } from '@/hooks/redux'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const useTrackUserSettings = () => {
const passwordRequirement = useAppSelector(selectEffectivePasswordRequirement)
const fiatCurrency = useAppSelector((s) => s.settings.fiatCurrency)
const network = useAppSelector((s) => s.network.name)
const region = useAppSelector((s) => s.settings.region)

useEffect(() => {
if (posthog.__loaded)
Expand All @@ -27,7 +28,8 @@ const useTrackUserSettings = () => {
language,
passwordRequirement,
fiatCurrency,
network
network,
region
})
}, [
devTools,
Expand All @@ -38,6 +40,7 @@ const useTrackUserSettings = () => {
passwordRequirement,
posthog.__loaded,
posthog.people,
region,
theme,
walletLockTimeInMinutes
])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { useTranslation } from 'react-i18next'
import KeyValueInput from '@/components/Inputs/InlineLabelValueInput'
import Select from '@/components/Inputs/Select'
import useAnalytics from '@/features/analytics/useAnalytics'
import useRegionOptions from '@/features/settings/regionSettings/useRegionOptions'
import { numberFormatRegionChanged } from '@/features/settings/settingsActions'
import useRegionOptions from '@/features/settings/useRegionOptions'
import { useAppDispatch, useAppSelector } from '@/hooks/redux'

const RegionSettings = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { upperFirst } from 'lodash'
import { useMemo } from 'react'

import { Language } from '@/features/localization/languages'
import { useAppSelector } from '@/hooks/redux'

import regionsLocales from './regions.json'
Expand All @@ -14,6 +13,11 @@ const useRegionOptions = () => {

export default useRegionOptions

const getRegionsOptions = (languageLocale: string) =>
regionsLocales
.map((regionLocale) => getRegionOption(regionLocale, languageLocale))
.sort((a, b) => a.label.localeCompare(b.label))

// Inspired by https://github.com/LedgerHQ/ledger-live/blob/065dda3/apps/ledger-live-desktop/src/renderer/screens/settings/sections/General/RegionSelect.tsx
const getRegionOption = (regionLocale: string, languageLocale: string | Intl.Locale) => {
const [language, region = ''] = regionLocale.split('-')
Expand All @@ -32,8 +36,3 @@ const getRegionOption = (regionLocale: string, languageLocale: string | Intl.Loc
label
}
}

const getRegionsOptions = (languageLocale: Language) =>
regionsLocales
.map((regionLocale) => getRegionOption(regionLocale, languageLocale))
.sort((a, b) => a.label.localeCompare(b.label))
2 changes: 2 additions & 0 deletions apps/mobile-wallet/locales/en-US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -456,5 +456,7 @@
"Splitting your funds into multiple addresses can help you stay organized and increase privacy.": "Splitting your funds into multiple addresses can help you stay organized and increase privacy.",
"No dApps match your search: \"{{ searchText }}\"": "No dApps match your search: \"{{ searchText }}\"",
"Visit dApp": "Visit dApp",
"Region": "Region",
"Choose your region to update formats of dates, time, and currencies.": "Choose your region to update formats of dates, time, and currencies.",
"More details on Alph.land": "More details on Alph.land"
}
2 changes: 2 additions & 0 deletions apps/mobile-wallet/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { DefaultTheme, ThemeProvider } from 'styled-components/native'
import ToastAnchor from '~/components/toasts/ToastAnchor'
import LoadingManager from '~/features/loader/LoadingManager'
import { useLocalization } from '~/features/localization/useLocalization'
import { useSystemRegion } from '~/features/settings/regionSettings/useSystemRegion'
import useLoadStoredSettings from '~/features/settings/useLoadStoredSettings'
import { useAppDispatch, useAppSelector } from '~/hooks/redux'
import { useAsyncData } from '~/hooks/useAsyncData'
Expand Down Expand Up @@ -129,6 +130,7 @@ const Main = ({ children, ...props }: ViewProps) => {
useLoadStoredSettings()
useInitializeClient()
useLocalization()
useSystemRegion()

useEffect(() => {
if (walletMetadata) dispatch(appLaunchedWithLastUsedWallet(walletMetadata))
Expand Down
62 changes: 36 additions & 26 deletions apps/mobile-wallet/src/components/Amount.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { convertToPositive, formatAmountForDisplay, formatFiatAmountForDisplay } from '@alephium/shared'
import { convertToPositive, formatAmountForDisplay } from '@alephium/shared'
import { useState } from 'react'
import { StyleProp, TextStyle } from 'react-native'

import { useAppSelector } from '~/hooks/redux'
Expand All @@ -9,7 +10,6 @@ export interface AmountProps extends AppTextProps {
value?: bigint | number
decimals?: number
isFiat?: boolean
fadeDecimals?: boolean
fullPrecision?: boolean
nbOfDecimalsToShow?: number
suffix?: string
Expand All @@ -24,7 +24,6 @@ export interface AmountProps extends AppTextProps {

const Amount = ({
value,
fadeDecimals,
fullPrecision = false,
suffix = '',
showOnDiscreetMode = false,
Expand All @@ -40,59 +39,70 @@ const Amount = ({
...props
}: AmountProps) => {
const discreetMode = useAppSelector((state) => state.settings.discreetMode)
const region = useAppSelector((state) => state.settings.region)
const fiatCurrency = useAppSelector((state) => state.settings.currency)

const [tappedToDisableDiscreetMode, setTappedToDisableDiscreetMode] = useState(false)

const hideAmount = discreetMode && !showOnDiscreetMode && !tappedToDisableDiscreetMode

const handleTappedToDisableDiscreetMode = () => setTappedToDisableDiscreetMode(!tappedToDisableDiscreetMode)

let quantitySymbol = ''
let amount = ''
let tinyAmount = ''
let isNegative = false
const color = props.color ?? (highlight && value !== undefined ? (value < 0 ? 'send' : 'receive') : 'primary')

if (value !== undefined) {
isNegative = value < 0

if (isFiat && typeof value === 'number') {
amount = formatFiatAmountForDisplay(isNegative ? value * -1 : value)
amount = new Intl.NumberFormat(region, { style: 'currency', currency: fiatCurrency }).format(value)

return (
<AppText {...props} {...{ color, style }} onPress={handleTappedToDisableDiscreetMode}>
{hideAmount ? '•••' : amount}
</AppText>
)
} else if (isUnknownToken) {
amount = convertToPositive(value as bigint).toString()
} else {
amount = formatAmountForDisplay({
amount: convertToPositive(value as bigint),
amountDecimals: decimals,
displayDecimals: nbOfDecimalsToShow,
fullPrecision
fullPrecision,
region
})
}

if (fadeDecimals && ['K', 'M', 'B', 'T'].some((char) => amount.endsWith(char))) {
quantitySymbol = amount.slice(-1)
amount = amount.slice(0, -1)
}
}

let [integralPart, fractionalPart] = amount.split('.')
const amountIsTooSmall = formatAmountForDisplay({
amount: convertToPositive(value as bigint),
amountDecimals: decimals,
displayDecimals: nbOfDecimalsToShow,
fullPrecision
}).startsWith('0.0000')

if (useTinyAmountShorthand && amount.startsWith('0.0000')) {
integralPart = '< 0'
fractionalPart = '0001'
tinyAmount =
useTinyAmountShorthand && amountIsTooSmall
? formatAmountForDisplay({ amount: BigInt(1), amountDecimals: 4, region })
: ''
}
}

const color = props.color ?? (highlight && value !== undefined ? (value < 0 ? 'send' : 'receive') : 'primary')
const fadedColor = fadeDecimals ? 'secondary' : color

return (
<AppText {...props} {...{ color, style }}>
{discreetMode && !showOnDiscreetMode ? (
<AppText {...props} {...{ color, style }} onPress={handleTappedToDisableDiscreetMode}>
{hideAmount ? (
'•••'
) : integralPart ? (
) : amount ? (
<>
{showPlusMinus && (
<AppText {...props} color={color}>
{isNegative ? '-' : '+'}
</AppText>
)}
<AppText {...props} color={color}>
{integralPart}
{tinyAmount ? `< ${tinyAmount}` : amount}
</AppText>
{fractionalPart && <AppText {...props} color={fadedColor}>{`.${fractionalPart}`}</AppText>}
{quantitySymbol && <AppText {...props} color={fadedColor}>{` ${quantitySymbol} `}</AppText>}
{!isUnknownToken && (
<AppText {...props} color={fadeSuffix ? 'secondary' : color}>{` ${suffix || 'ALPH'}`}</AppText>
)}
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile-wallet/src/components/ConsolidationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const ConsolidationModal = withModal<ConsolidationModalProps>(({ id, onConsolida
</AppText>
<Fee>
<AppText>{t('Fee')}:</AppText>
<Amount value={fees} fullPrecision fadeDecimals bold />
<Amount value={fees} fullPrecision bold />
</Fee>
</View>
</ScreenSection>
Expand Down
3 changes: 3 additions & 0 deletions apps/mobile-wallet/src/features/modals/AppModals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import TokenAmountModal from '~/features/send/modals/TokenAmountModal'
import CurrencySelectModal from '~/features/settings/CurrencySelectModal'
import EditWalletNameModal from '~/features/settings/EditWalletNameModal'
import MnemonicModal from '~/features/settings/MnemonicModal'
import RegionSelectModal from '~/features/settings/regionSettings/RegionSelectModal'
import SafePlaceWarningModal from '~/features/settings/SafePlaceWarningModal'
import WalletDeleteModal from '~/features/settings/WalletDeleteModal'
import TransactionModal from '~/features/transactionsDisplay/TransactionModal'
Expand Down Expand Up @@ -130,6 +131,8 @@ const AppModals = () => {
return <DAppQuickActionsModal key={id} id={id} {...params.props} />
case 'DAppDetailsModal':
return <DAppDetailsModal key={id} id={id} {...params.props} />
case 'RegionSelectModal':
return <RegionSelectModal key={id} id={id} />
default:
return null
}
Expand Down
4 changes: 3 additions & 1 deletion apps/mobile-wallet/src/features/modals/modalTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import TokenAmountModal from '~/features/send/modals/TokenAmountModal'
import CurrencySelectModal from '~/features/settings/CurrencySelectModal'
import EditWalletNameModal from '~/features/settings/EditWalletNameModal'
import MnemonicModal from '~/features/settings/MnemonicModal'
import RegionSelectModal from '~/features/settings/regionSettings/RegionSelectModal'
import SafePlaceWarningModal from '~/features/settings/SafePlaceWarningModal'
import WalletDeleteModal from '~/features/settings/WalletDeleteModal'
import TransactionModal from '~/features/transactionsDisplay/TransactionModal'
Expand Down Expand Up @@ -76,7 +77,8 @@ export const ModalComponents = {
AddressQRCodeScanActionsModal,
AddressPickerQuickActionsModal,
DAppQuickActionsModal,
DAppDetailsModal
DAppDetailsModal,
RegionSelectModal
}

export type ModalName = keyof typeof ModalComponents
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { FlashList } from '@shopify/flash-list'
import { useTranslation } from 'react-i18next'

import { sendAnalytics } from '~/analytics'
import RadioButtonRow from '~/components/RadioButtonRow'
import BottomModalFlashList from '~/features/modals/BottomModalFlashList'
import { closeModal } from '~/features/modals/modalActions'
import withModal from '~/features/modals/withModal'
import { numberFormatRegionChanged } from '~/features/settings/regionSettings/regionSettingsActions'
import { regionOptions } from '~/features/settings/regionSettings/regionsUtils'
import { useAppDispatch, useAppSelector } from '~/hooks/redux'

const RegionSelectModal = withModal(({ id }) => {
const dispatch = useAppDispatch()
const { t } = useTranslation()
const currentRegion = useAppSelector((s) => s.settings.region)

const handleRegionChange = (region: string) => {
dispatch(numberFormatRegionChanged(region))
dispatch(closeModal({ id }))
sendAnalytics({ event: 'Region changed', props: { region } })
}

return (
<BottomModalFlashList
modalId={id}
title={t('Region')}
flashListRender={(props) => (
<FlashList
data={regionOptions}
estimatedItemSize={65}
renderItem={({ item: regionOption, index }) => (
<RadioButtonRow
key={regionOption.label}
title={regionOption.label}
onPress={() => handleRegionChange(regionOption.value)}
isActive={currentRegion === regionOption.value}
isLast={index === regionOptions.length - 1}
/>
)}
{...props}
/>
)}
/>
)
})

export default RegionSelectModal
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useTranslation } from 'react-i18next'

import AppText from '~/components/AppText'
import Row from '~/components/Row'
import { openModal } from '~/features/modals/modalActions'
import { regionOptions } from '~/features/settings/regionSettings/regionsUtils'
import { useAppDispatch, useAppSelector } from '~/hooks/redux'

const RegionSettingsRow = () => {
const currentRegion = useAppSelector((s) => s.settings.region)
const { t } = useTranslation()
const dispatch = useAppDispatch()

const openRegionSelectModal = () => dispatch(openModal({ name: 'RegionSelectModal' }))

return (
<Row onPress={openRegionSelectModal} title={t('Region')}>
<AppText bold style={{ textAlign: 'right' }}>
{regionOptions.find((region) => region.value === currentRegion)?.label}
</AppText>
</Row>
)
}

export default RegionSettingsRow
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createAction } from '@reduxjs/toolkit'

import { Settings } from '~/types/settings'

export const numberFormatRegionChanged = createAction<Settings['general']['region']>(
'settings/numberFormatRegionChanged'
)

export const systemRegionMatchSucceeded = createAction<string>('settings/systemRegionMatchSucceeded')

export const systemRegionMatchFailed = createAction('settings/systemRegionMatchFailed')
Loading