-
Notifications
You must be signed in to change notification settings - Fork 293
Zcash: Orchard → Ironwood (NU6.3) migration UX #6077
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
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import { describe, expect, it } from '@jest/globals' | ||
| import type { EdgeCurrencyWallet } from 'edge-core-js' | ||
|
|
||
| import { getZcashMigrationStatus } from '../util/zcashMigration' | ||
|
|
||
|
peachbits marked this conversation as resolved.
|
||
| const goodStatus = { | ||
| state: 'required', | ||
| completedTransfers: 0, | ||
| totalTransfers: 0, | ||
| remainingOrchardZatoshi: '123', | ||
| hasOverdueTransfers: false, | ||
| isSynced: true | ||
| } | ||
|
|
||
| const makeFakeWallet = (opts: { | ||
| pluginId: string | ||
| otherMethods?: object | ||
| }): EdgeCurrencyWallet => | ||
| ({ | ||
| currencyInfo: { pluginId: opts.pluginId }, | ||
| otherMethods: opts.otherMethods ?? {} | ||
| } as any) | ||
|
|
||
| describe('zcashMigration util', () => { | ||
| it('returns status for a migration-capable zcash wallet', async () => { | ||
| const wallet = makeFakeWallet({ | ||
| pluginId: 'zcash', | ||
| otherMethods: { | ||
| getMigrationStatus: async () => goodStatus | ||
| } | ||
| }) | ||
| const status = await getZcashMigrationStatus(wallet) | ||
| expect(status?.state).toBe('required') | ||
| expect(status?.remainingOrchardZatoshi).toBe('123') | ||
| }) | ||
|
|
||
| it('returns undefined for non-zcash wallets', async () => { | ||
| const wallet = makeFakeWallet({ | ||
| pluginId: 'bitcoin', | ||
| otherMethods: { getMigrationStatus: async () => goodStatus } | ||
| }) | ||
| expect(await getZcashMigrationStatus(wallet)).toBeUndefined() | ||
| }) | ||
|
|
||
| it('returns undefined when the engine lacks the method (old accountbased)', async () => { | ||
| const wallet = makeFakeWallet({ pluginId: 'zcash' }) | ||
| expect(await getZcashMigrationStatus(wallet)).toBeUndefined() | ||
| }) | ||
|
|
||
| it('returns undefined when the engine call throws', async () => { | ||
| const wallet = makeFakeWallet({ | ||
| pluginId: 'zcash', | ||
| otherMethods: { | ||
| getMigrationStatus: async () => { | ||
| throw new Error('engine broke') | ||
| } | ||
| } | ||
| }) | ||
| expect(await getZcashMigrationStatus(wallet)).toBeUndefined() | ||
| }) | ||
|
|
||
| it('returns undefined on malformed status shapes', async () => { | ||
| const wallet = makeFakeWallet({ | ||
| pluginId: 'zcash', | ||
| otherMethods: { | ||
| getMigrationStatus: async () => ({ state: 'bogus' }) | ||
| } | ||
| }) | ||
| expect(await getZcashMigrationStatus(wallet)).toBeUndefined() | ||
| }) | ||
| }) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| import * as React from 'react' | ||
| import { View } from 'react-native' | ||
| import IonIcon from 'react-native-vector-icons/Ionicons' | ||
| import { sprintf } from 'sprintf-js' | ||
|
|
||
| import { useHandler } from '../../hooks/useHandler' | ||
| import { lstrings } from '../../locales/strings' | ||
| import { config } from '../../theme/appConfig' | ||
| import { openBrowserUri } from '../../util/WebUtils' | ||
| import { EdgeButton } from '../buttons/EdgeButton' | ||
| import { showError } from '../services/AirshipInstance' | ||
| import { cacheStyles, type Theme, useTheme } from '../services/ThemeContext' | ||
| import { EdgeText } from '../themed/EdgeText' | ||
| import { EdgeCard } from './EdgeCard' | ||
|
|
||
| const ZCASH_MIGRATION_HELP_URI = 'https://support.edge.app/articles/16111542' | ||
|
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| interface Props { | ||
| /** | ||
| * The Orchard-pool balance at risk, pre-formatted with its denomination. | ||
| * ZIP 318 requires the entry point to show this specific figure rather than | ||
| * the wallet's whole shielded balance, since only Orchard funds cross the | ||
| * turnstile. | ||
| */ | ||
| orchardBalanceText: string | ||
| onMigratePress: () => Promise<void> | void | ||
| } | ||
|
|
||
| /** | ||
| * Orchard -> Ironwood (NU6.3) migration card for the Zcash wallet scene. | ||
| * | ||
| * Its own component rather than an `AlertCardUi4` because the help link is | ||
| * inline in the copy rather than a second button, which that card cannot do. | ||
| * | ||
| * Not dismissable: the card clears on its own once the Orchard balance empties, | ||
| * by the sweep or by ordinary spends draining it passively. | ||
| */ | ||
| export const ZcashMigrationCard: React.FC<Props> = props => { | ||
| const { orchardBalanceText, onMigratePress } = props | ||
| const theme = useTheme() | ||
| const styles = getStyles(theme) | ||
|
|
||
| // Returned, not swallowed: EdgeButton's usePendingPress only shows the spinner | ||
| // and blocks re-taps when it receives a thenable, and it reports errors itself. | ||
| // Preparing the sweep does real work (getAddresses, getMaxSpendable), so the | ||
| // button must not stay tappable through it. | ||
| const handleMigrate = useHandler(async (): Promise<void> => { | ||
| await onMigratePress() | ||
| }) | ||
|
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| const handleLearnMore = useHandler(() => { | ||
| const uri = config.zcashMigrationLearnMoreUrl ?? ZCASH_MIGRATION_HELP_URI | ||
| openBrowserUri(uri).catch((error: unknown) => { | ||
| showError(error) | ||
| }) | ||
| }) | ||
|
|
||
| return ( | ||
| <EdgeCard | ||
| gradientBackground={theme.cardGradientWarning} | ||
| marginRem={[0.5, 0.5, 0, 0.5]} | ||
| > | ||
| <View style={styles.container}> | ||
| <View style={styles.titleContainer}> | ||
| <IonIcon | ||
| name="warning-outline" | ||
| style={styles.icon} | ||
| color={theme.primaryText} | ||
| size={theme.rem(1.25)} | ||
| /> | ||
| <EdgeText numberOfLines={0} style={styles.titleText}> | ||
| {lstrings.zcash_migration_recommended_title} | ||
| </EdgeText> | ||
| </View> | ||
|
|
||
| {/* | ||
| The help link is inline at the end of the copy rather than a second | ||
| button, so the card keeps a single call to action. Nested EdgeText with | ||
| its own onPress, per the Stealth Send treatment. | ||
| */} | ||
| <EdgeText style={styles.text} numberOfLines={10}> | ||
| {sprintf( | ||
| lstrings.zcash_migration_recommended_body_1s, | ||
| orchardBalanceText | ||
| )}{' '} | ||
| <EdgeText style={styles.learnMoreLink} onPress={handleLearnMore}> | ||
| {lstrings.zcash_migration_learn_more_button} | ||
| </EdgeText> | ||
| </EdgeText> | ||
|
|
||
| <View style={styles.buttonContainer}> | ||
| <EdgeButton | ||
| label={lstrings.zcash_migration_recommended_button} | ||
| layout="solo" | ||
| mini | ||
| onPress={handleMigrate} | ||
| type="primary" | ||
| /> | ||
| </View> | ||
| </View> | ||
| </EdgeCard> | ||
| ) | ||
| } | ||
|
|
||
| const getStyles = cacheStyles((theme: Theme) => ({ | ||
| container: { | ||
| margin: theme.rem(0.5) | ||
| }, | ||
| titleContainer: { | ||
| flexDirection: 'row', | ||
| alignItems: 'center' | ||
| }, | ||
| titleText: { | ||
| marginLeft: theme.rem(0.2), | ||
| fontFamily: theme.fontFaceMedium, | ||
| flexShrink: 1 | ||
| }, | ||
| icon: { | ||
| marginRight: theme.rem(0.2) | ||
| }, | ||
| text: { | ||
| fontSize: theme.rem(0.75), | ||
| marginHorizontal: theme.rem(0.25), | ||
| marginTop: theme.rem(0.5) | ||
| }, | ||
| learnMoreLink: { | ||
| fontSize: theme.rem(0.75), | ||
| color: theme.textLink | ||
| }, | ||
| buttonContainer: { | ||
| marginTop: theme.rem(1) | ||
| } | ||
| })) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,7 +66,13 @@ export function EdgeCarousel<T>(props: Props<T>): React.ReactElement { | |
| <View style={containerStyle}> | ||
| {data.map((item, itemIndex) => ( | ||
| <ItemBox | ||
| key={keyExtractor(item, itemIndex)} | ||
| // The index is part of the identity on purpose. An item's whole | ||
| // position comes from an animated transform, and that transform | ||
| // is not re-applied when a surviving item shifts slots: removing | ||
| // an item leaves the ones after it parked at their old offsets, | ||
| // a full item-width off-screen. Remounting on a slot change | ||
| // establishes the transform fresh, which is always correct. | ||
| key={`${itemIndex}-${keyExtractor(item, itemIndex)}`} | ||
|
Contributor
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. The previous issue was a PITA too here. Great catch, thanks. |
||
| boxStyle={boxStyle} | ||
| itemIndex={itemIndex} | ||
| itemWidth={itemWidth} | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Fold into the last commit