-
Notifications
You must be signed in to change notification settings - Fork 283
Add PocketChange UI for Monero wallet #5941
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
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| import * as React from 'react' | ||
| import { View } from 'react-native' | ||
| import type { AirshipBridge } from 'react-native-airship' | ||
|
|
||
| import { useHandler } from '../../hooks/useHandler' | ||
| import { lstrings } from '../../locales/strings' | ||
| import { EdgeButton } from '../buttons/EdgeButton' | ||
| import { cacheStyles, type Theme, useTheme } from '../services/ThemeContext' | ||
| import { SettingsHeaderRow } from '../settings/SettingsHeaderRow' | ||
| import { SettingsSwitchRow } from '../settings/SettingsSwitchRow' | ||
| import { EdgeText, Paragraph } from '../themed/EdgeText' | ||
| import { EdgeModal } from './EdgeModal' | ||
|
|
||
| const POCKET_AMOUNTS_XMR = [0.1, 0.2, 0.3, 0.5, 0.8, 1.3] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue: Floating-point numbers used for crypto amounts: Recommendation: Store amounts as strings in the coin's smallest unit (piconero for XMR), or at minimum as integer multipliers of a fixed base unit. Convert to display strings using the same |
||
|
|
||
| export interface PocketChangeConfig { | ||
| enabled: boolean | ||
| amountIndex: number | ||
| } | ||
|
|
||
| interface Props { | ||
| bridge: AirshipBridge<PocketChangeConfig | undefined> | ||
| initialConfig: PocketChangeConfig | ||
| } | ||
|
|
||
| export const PocketChangeModal: React.FC<Props> = props => { | ||
| const { bridge, initialConfig } = props | ||
| const theme = useTheme() | ||
| const styles = getStyles(theme) | ||
| const [enabled, setEnabled] = React.useState(initialConfig.enabled) | ||
| const [amountIndex, setAmountIndex] = React.useState( | ||
| initialConfig.amountIndex | ||
| ) | ||
|
|
||
| const handleCancel = useHandler((): void => { | ||
| bridge.resolve(undefined) | ||
| }) | ||
|
|
||
| const handleSave = useHandler((): void => { | ||
| bridge.resolve({ enabled, amountIndex }) | ||
| }) | ||
|
|
||
| const handleToggle = useHandler((): void => { | ||
| setEnabled(prev => !prev) | ||
| }) | ||
|
|
||
| const handleDecrease = useHandler((): void => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: No unit tests cover the stepper bounds logic ( Recommendation: Add a test file at |
||
| setAmountIndex(prev => Math.max(0, prev - 1)) | ||
| }) | ||
|
|
||
| const handleIncrease = useHandler((): void => { | ||
| setAmountIndex(prev => Math.min(POCKET_AMOUNTS_XMR.length - 1, prev + 1)) | ||
| }) | ||
|
|
||
| return ( | ||
| <EdgeModal | ||
| bridge={bridge} | ||
| onCancel={handleCancel} | ||
| title={lstrings.pocketchange_title} | ||
| > | ||
| <View style={styles.container}> | ||
| <Paragraph>{lstrings.pocketchange_description}</Paragraph> | ||
|
|
||
| <SettingsSwitchRow | ||
| label={lstrings.pocketchange_enable} | ||
| value={enabled} | ||
| onPress={handleToggle} | ||
| /> | ||
|
|
||
| {enabled ? ( | ||
| <> | ||
| <SettingsHeaderRow label={lstrings.pocketchange_amount_header} /> | ||
|
|
||
| <View style={styles.stepperRow}> | ||
| <EdgeButton | ||
| label="−" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: "−" and "+" button labels are hardcoded strings not sourced from locale strings. Recommendation: Add |
||
| type="secondary" | ||
| mini | ||
| onPress={handleDecrease} | ||
| disabled={amountIndex <= 0} | ||
| /> | ||
| <EdgeText style={styles.amountText}> | ||
| {POCKET_AMOUNTS_XMR[amountIndex]} XMR{' '} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Warning: "XMR" is a hardcoded currency code in user-facing text. This modal is Monero-specific via Recommendation: Pass |
||
| {lstrings.pocketchange_per_pocket} | ||
| </EdgeText> | ||
| <EdgeButton | ||
| label="+" | ||
| type="secondary" | ||
| mini | ||
| onPress={handleIncrease} | ||
| disabled={amountIndex >= POCKET_AMOUNTS_XMR.length - 1} | ||
| /> | ||
| </View> | ||
|
|
||
| <Paragraph>{lstrings.pocketchange_explainer}</Paragraph> | ||
| </> | ||
| ) : null} | ||
|
|
||
| <View style={styles.saveButton}> | ||
| <EdgeButton label={lstrings.pocketchange_save} onPress={handleSave} /> | ||
| </View> | ||
| </View> | ||
| </EdgeModal> | ||
| ) | ||
| } | ||
|
|
||
| const getStyles = cacheStyles((theme: Theme) => ({ | ||
| container: { | ||
| paddingHorizontal: theme.rem(0.5) | ||
| }, | ||
| stepperRow: { | ||
| flexDirection: 'row', | ||
| alignItems: 'center', | ||
| justifyContent: 'center', | ||
| paddingVertical: theme.rem(0.5) | ||
| }, | ||
| amountText: { | ||
| fontSize: theme.rem(1.1), | ||
| marginHorizontal: theme.rem(1) | ||
| }, | ||
| saveButton: { | ||
| marginTop: theme.rem(1), | ||
| marginBottom: theme.rem(0.5) | ||
| } | ||
| })) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,11 +26,12 @@ import { | |
| import { getWalletName } from '../../util/CurrencyWalletHelpers' | ||
| import { EdgeTouchableOpacity } from '../common/EdgeTouchableOpacity' | ||
| import { CryptoIcon } from '../icons/CryptoIcon' | ||
| import { showError } from '../services/AirshipInstance' | ||
| import { Airship, showError } from '../services/AirshipInstance' | ||
| import { cacheStyles, type Theme, useTheme } from '../services/ThemeContext' | ||
| import { UnscaledText } from '../text/UnscaledText' | ||
| import { ModalTitle } from '../themed/ModalParts' | ||
| import { EdgeModal } from './EdgeModal' | ||
| import { type PocketChangeConfig, PocketChangeModal } from './PocketChangeModal' | ||
|
|
||
| interface Option { | ||
| value: WalletListMenuKey | ||
|
|
@@ -53,6 +54,7 @@ const icons: Record<string, string> = { | |
| getSeed: 'key', | ||
| goToParent: 'upcircleo', | ||
| manageTokens: 'plus', | ||
| pocketChange: 'wallet', | ||
| rawDelete: 'warning', | ||
| rename: 'edit', | ||
| resync: 'sync', | ||
|
|
@@ -75,6 +77,11 @@ export const WALLET_LIST_MENU: Array<{ | |
| label: lstrings.settings_asset_settings, | ||
| value: 'settings' | ||
| }, | ||
| { | ||
| pluginIds: ['monero'], | ||
| label: lstrings.pocketchange_menu_item, | ||
| value: 'pocketChange' | ||
| }, | ||
| { | ||
| label: lstrings.string_rename, | ||
| value: 'rename' | ||
|
|
@@ -142,7 +149,7 @@ export const WALLET_LIST_MENU: Array<{ | |
| } | ||
| ] | ||
|
|
||
| export function WalletListMenuModal(props: Props) { | ||
| export function WalletListMenuModal(props: Props): React.ReactElement { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Warning: Recommendation: Change to |
||
| const { bridge, tokenId, navigation, walletId } = props | ||
|
|
||
| const [options, setOptions] = React.useState<Option[]>([]) | ||
|
|
@@ -161,13 +168,41 @@ export function WalletListMenuModal(props: Props) { | |
| const theme = useTheme() | ||
| const styles = getStyles(theme) | ||
|
|
||
| const handleCancel = () => { | ||
| const handleCancel = (): void => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Warning: Recommendation: Wrap with |
||
| props.bridge.resolve() | ||
| } | ||
|
|
||
| const optionAction = useHandler(async (option: WalletListMenuKey) => { | ||
| if (loadingOption != null) return // Prevent multiple actions | ||
|
|
||
| if (option === 'pocketChange' && wallet != null) { | ||
| setLoadingOption(option) | ||
| try { | ||
| let initialConfig: PocketChangeConfig = { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Warning: The Recommendation: Export a |
||
| enabled: false, | ||
| amountIndex: 2 | ||
| } | ||
| if (wallet.otherMethods?.getPocketChangeSetting != null) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Warning: Recommendation: Define a local interface |
||
| const saved = await wallet.otherMethods.getPocketChangeSetting() | ||
| if (saved != null) initialConfig = saved | ||
| } | ||
| const result = await Airship.show<PocketChangeConfig | undefined>(b => ( | ||
| <PocketChangeModal bridge={b} initialConfig={initialConfig} /> | ||
| )) | ||
| if ( | ||
| result != null && | ||
| wallet.otherMethods?.setPocketChangeSetting != null | ||
| ) { | ||
| await wallet.otherMethods.setPocketChangeSetting(result) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Warning: In the Recommendation: Add |
||
| } | ||
| bridge.resolve() | ||
| } catch (error) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Warning: Recommendation: Change to |
||
| setLoadingOption(null) | ||
| showError(error) | ||
| } | ||
| return | ||
| } | ||
|
|
||
| setLoadingOption(option) | ||
| try { | ||
| await dispatch( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Warning: Dead code: the
case 'pocketChange'handler returns an empty no-op.WalletListMenuModalintercepts and handlespocketChangewith an earlyreturnbefore ever callingdispatch(walletListMenuAction(...)), so this branch is never reached.Recommendation: Remove the
case 'pocketChange'block entirely (let it fall through todefault) to avoid misleading future readers. If keeping it, add a comment making it explicit that this path is intentionally unreachable.