From b3e78262b127d63d6c093573c9e18fdb5802aaa5 Mon Sep 17 00:00:00 2001 From: minibits-cash Date: Tue, 24 Sep 2024 22:02:00 +0200 Subject: [PATCH] Wallet screen redesign MVP --- patches/react-native-tab-view+3.5.2.patch | 26 + src/models/NwcStore.ts | 5 +- src/screens/ReceiveScreen.tsx | 5 +- src/screens/TranDetailScreen.tsx | 14 +- src/screens/Wallet/CurrencyAmount.tsx | 2 +- src/screens/WalletScreen.tsx | 635 +++++++++++++--------- src/theme/colors.ts | 12 +- yarn.lock | 9 +- 8 files changed, 437 insertions(+), 271 deletions(-) create mode 100644 patches/react-native-tab-view+3.5.2.patch diff --git a/patches/react-native-tab-view+3.5.2.patch b/patches/react-native-tab-view+3.5.2.patch new file mode 100644 index 00000000..67a7008d --- /dev/null +++ b/patches/react-native-tab-view+3.5.2.patch @@ -0,0 +1,26 @@ +diff --git a/node_modules/react-native-tab-view/src/TabBar.tsx b/node_modules/react-native-tab-view/src/TabBar.tsx +index e8d0b4c..1c3c09f 100644 +--- a/node_modules/react-native-tab-view/src/TabBar.tsx ++++ b/node_modules/react-native-tab-view/src/TabBar.tsx +@@ -364,8 +364,7 @@ export function TabBar({ + + const renderItem = React.useCallback( + ({ item: route, index }: ListRenderItemInfo) => { +- const props: TabBarItemProps & { key: string } = { +- key: route.key, ++ const props: TabBarItemProps = { + position: position, + route: route, + navigationState: navigationState, +@@ -446,9 +445,9 @@ export function TabBar({ + <> + {gap > 0 && index > 0 ? : null} + {renderTabBarItem ? ( +- renderTabBarItem(props) ++ renderTabBarItem({ key: route.key, ...props }) + ) : ( +- ++ + )} + + ); diff --git a/src/models/NwcStore.ts b/src/models/NwcStore.ts index 3ae86437..dcf0695b 100644 --- a/src/models/NwcStore.ts +++ b/src/models/NwcStore.ts @@ -74,8 +74,9 @@ const nwcPngUrl = 'https://1044827509-files.gitbook.io/~/files/v0/b/gitbook-x-pr const getConnectionRelays = function () { const minibitsRelays = NostrClient.getMinibitsRelays() - const publicRelays = NostrClient.getDefaultRelays() - return [...minibitsRelays, ...publicRelays] + // const publicRelays = NostrClient.getDefaultRelays() + // return [...minibitsRelays, ...publicRelays] + return minibitsRelays } const getSupportedMethods = function () { diff --git a/src/screens/ReceiveScreen.tsx b/src/screens/ReceiveScreen.tsx index 20c91938..6b5de849 100644 --- a/src/screens/ReceiveScreen.tsx +++ b/src/screens/ReceiveScreen.tsx @@ -303,8 +303,7 @@ export const ReceiveScreen: FC> = observer( {transactionStatus !== TransactionStatus.COMPLETED && ( {CashuUtils.getMintsFromToken(token).map((mintUrl, index) => { @@ -526,7 +525,7 @@ const $contentContainer: TextStyle = { const $card: ViewStyle = { // marginTop: - spacing.extraLarge * 2, marginBottom: spacing.small, - paddingTop: 0, + // paddingTop: 0, } const $memoCard: ViewStyle = { diff --git a/src/screens/TranDetailScreen.tsx b/src/screens/TranDetailScreen.tsx index d7519ec4..7446865a 100644 --- a/src/screens/TranDetailScreen.tsx +++ b/src/screens/TranDetailScreen.tsx @@ -1833,10 +1833,14 @@ export const TranItem = function (props: { {props.isCurrency && props.unit ? ( - + ) : ( <> {props.url ? ( @@ -1844,7 +1848,11 @@ export const TranItem = function (props: { ) : ( - + )} )} diff --git a/src/screens/Wallet/CurrencyAmount.tsx b/src/screens/Wallet/CurrencyAmount.tsx index 82a1c216..885addf7 100644 --- a/src/screens/Wallet/CurrencyAmount.tsx +++ b/src/screens/Wallet/CurrencyAmount.tsx @@ -47,7 +47,7 @@ export const CurrencyAmount = observer(function (props: { {} -type RelayStatus = { - relay: string, - status: number, - error?: string -} - export const WalletScreen: FC = observer( function WalletScreen({route, navigation}) { const { @@ -87,20 +76,29 @@ export const WalletScreen: FC = observer( userSettingsStore, nwcStore, walletProfileStore - } = useStores() - - const pagerRef = useRef(null) + } = useStores() + const appState = useRef(AppState.currentState) const isInternetReachable = useIsInternetReachable() const groupedMints: MintsByUnit[] = mintsStore.groupedByUnit + // Tab view by unit + const [routes] = useState( + groupedMints.map((mintUnit) => ({ + key: mintUnit.unit, + title: getCurrency(mintUnit.unit).code, + })) + ) + const [tabIndex, setTabIndex] = useState(0) const [currentUnit, setCurrentUnit] = useState(groupedMints.length > 0 ? groupedMints[0].unit : 'sat') + const [info, setInfo] = useState('') const [defaultMintUrl, setDefaultMintUrl] = useState(MINIBITS_MINT_URL) const [lastClaimCheck, setLastClaimCheck] = useState(getUnixTime(new Date())) const [error, setError] = useState() const [isLoading, setIsLoading] = useState(false) + const [isMintsModalVisible, setIsMintsModalVisible] = useState(false) const [isUpdateAvailable, setIsUpdateAvailable] = useState(false) const [isUpdateModalVisible, setIsUpdateModalVisible] = useState(false) const [isSendModalVisible, setIsSendModalVisible] = useState(false) @@ -164,10 +162,13 @@ export const WalletScreen: FC = observer( WalletTask.handleInFlight().catch(e => false) // Create websocket subscriptions to receive tokens or payment requests by NOSTR DMs WalletTask.receiveEventsFromRelays().catch(e => false) - // log.trace('[getInitialData]', 'walletProfile', walletProfileStore) + + // Set wallet tab to preferred unit const preferredUnit: MintUnit = userSettingsStore.preferredUnit - const pageIndex = groupedMints.findIndex((m) => m.unit === preferredUnit) - pagerRef.current && pagerRef.current.setPage(pageIndex) + const preferredTabIndex = routes.findIndex(route => route.key === preferredUnit) + if(tabIndex !== preferredTabIndex) { + onTabChange(preferredTabIndex) + } // Create websocket subscriptions to receive NWC requests from remote wallets (if any) // go through websockets only if remote notifications not working as push data messages are @@ -183,6 +184,24 @@ export const WalletScreen: FC = observer( }, []) + const onTabChange = (tabIndex: number) => { + if(groupedMints.length === 0) { + return + } + + const currentUnit = routes[tabIndex].key + log.trace('[onTabChange] currentUnit', currentUnit) + + setCurrentUnit(currentUnit) + setTabIndex(tabIndex) + + const preferredUnit = userSettingsStore.preferredUnit + if(currentUnit !== preferredUnit) { // prevents db write on first load + userSettingsStore.setPreferredUnit(currentUnit) + } + } + + const handleDeeplink = async function ({url}: {url: string}) { try { @@ -286,6 +305,11 @@ export const WalletScreen: FC = observer( }, []) + const toggleMintsModal = () => { + setIsMintsModalVisible(previousState => !previousState) + } + + const toggleUpdateModal = () => { setIsUpdateModalVisible(previousState => !previousState) } @@ -325,16 +349,6 @@ export const WalletScreen: FC = observer( } } - const gotoSend = function () { - toggleSendModal() - navigation.navigate('Send', {unit: currentUnit}) - } - - const gotoLightningPay = async function () { - toggleSendModal() - navigation.navigate('LightningPay', {unit: currentUnit}) - } - const gotoScan = function () { navigation.navigate('Scan') } @@ -344,11 +358,6 @@ export const WalletScreen: FC = observer( navigation.navigate('TokenReceive', {unit: currentUnit}) } - const gotoTopup = function () { - toggleReceiveModal() - navigation.navigate('Topup', {unit: currentUnit}) - } - const gotoTranDetail = function (id: number) { // navigation.navigate('TranDetail', {id}) navigation.navigate('TransactionsNavigator', {screen: 'TranDetail', params: {id}, initial: false}) @@ -362,56 +371,120 @@ export const WalletScreen: FC = observer( navigation.navigate('ContactsNavigator', {screen: 'Profile', params: {}, initial: false}) } + const gotoMintInfo = function (mintUrl: string) { + setIsMintsModalVisible(false) + navigation.navigate('SettingsNavigator', { + screen: 'MintInfo', + params: {mintUrl}, + initial: false + }) + } + + const gotoTopup = function (mintUrl?: string) { + if(mintUrl) { + setIsMintsModalVisible(false) + navigation.navigate('Topup', { + mintUrl, + unit: currentUnit + }) + } else { + setIsReceiveModalVisible(false) + navigation.navigate('Topup', { + unit: currentUnit + }) + } + } + + + const gotoLightningPay = function (mintUrl?: string, ) { + if(mintUrl) { + setIsMintsModalVisible(false) + navigation.navigate('LightningPay', { + mintUrl, + unit: currentUnit + }) + } else { + setIsSendModalVisible(false) + navigation.navigate('LightningPay', { + unit: currentUnit + }) + } + } + + + const gotoSend = function () { + // log.trace('[gotoSend]', {currentUnit}) + setIsSendModalVisible(false) + navigation.navigate('Send', { + unit: currentUnit + }) + } + const handleError = function (e: AppError) { setIsLoading(false) setError(e) } - // Tab view by unit - const [routes] = useState( - groupedMints.map((mintUnit) => ({ - key: mintUnit.unit, - title: getCurrency(mintUnit.unit).code, - })) - ) - const renderScene = function ({ route }: { route: { key: string } }) { - const unitMints = groupedMints.find((mintUnit) => mintUnit.unit === route.key); + const renderUnitTabs = function ({ route }: { route: { key: string } }) { + const unitMints = groupedMints.find((mintUnit) => mintUnit.unit === route.key) if (unitMints) { return ( - - balance.unit === unitMints.unit)!} + <> + + balance.unit === unitMints.unit)!} + /> + + - - - {transactionsStore.recentByUnit(unitMints.unit).length > 0 && unitMints.mints.length < 4 && ( - { - return ( gotoTranDetail(item.id!)} - />) - } - } - // style={{ maxHeight: 300 - (mints.mints.length > 1 ? mints.mints.length * 38 : 0)}} - /> - } - style={[$card, {paddingTop: spacing.extraSmall}]} + + + + {transactionsStore.recentByUnit(unitMints.unit).length > 0 ? ( + { + return ( + gotoTranDetail(item.id!)} + /> + )} + } + /> + } + style={[$card, {paddingTop: spacing.extraSmall}]} + /> + ) : ( + - )} - - + } + style={[$card, {paddingTop: spacing.extraSmall, minHeight: 80}]} + /> + )} + + ) } return null; @@ -419,24 +492,40 @@ export const WalletScreen: FC = observer( const tabWidth = moderateScale(80) - const renderTabBar = (props: any) => ( - - - + const getActiveTabColor = (state: NavigationState) => { + switch (state.routes[tabIndex].key) { + case 'usd': + return useThemeColor('usd') + case 'eur': + return useThemeColor('eur') + default: + return useThemeColor('btc') + } + + } + + const renderTabBar = (props: any) => { + return( + + + ( + + )} + indicatorStyle={{ backgroundColor: getActiveTabColor(props.navigationState) }} + style={{backgroundColor: headerBg, shadowColor: 'transparent'}} + /> + - - ) - - - const [index, setIndex] = useState(0) - const headerBg = useThemeColor('header') - const activeTabIndicator = colors.palette.accent400 + ) + } + + const headerBg = useThemeColor('header') const balances = proofsStore.getBalances() const screenBg = useThemeColor('background') const mainButtonIcon = useThemeColor('button') @@ -481,10 +570,10 @@ export const WalletScreen: FC = observer( ) : ( )} @@ -535,9 +624,24 @@ export const WalletScreen: FC = observer( {error && } {isLoading && } + } + onBackButtonPress={toggleMintsModal} + onBackdropPress={toggleMintsModal} + /> + @@ -551,9 +655,9 @@ export const WalletScreen: FC = observer( subTx="updateModal.desc" onPress={gotoUpdate} /> - } - onBackButtonPress={toggleUpdateModal} - onBackdropPress={toggleUpdateModal} + } + onBackButtonPress={toggleUpdateModal} + onBackdropPress={toggleUpdateModal} /> = observer( leftIcon='faBolt' tx="walletScreen.payWithLightning" subTx="walletScreen.payWithLightningDesc" - onPress={gotoLightningPay} + onPress={() => gotoLightningPay()} /> } @@ -594,7 +698,7 @@ export const WalletScreen: FC = observer( leftIcon='faBolt' tx='walletScreen.topupWithLightning' subTx="walletScreen.topupWithLightningDesc" - onPress={gotoTopup} + onPress={() => gotoTopup()} /> } @@ -610,26 +714,32 @@ export const WalletScreen: FC = observer( const UnitBalanceBlock = observer(function (props: { unitBalance: UnitBalance -}) { - const headerBg = useThemeColor('header') - +}) { const balanceColor = 'white' - const currencyColor = colors.palette.primary200 + const convertedBalanceColor = colors.palette.primary200 + const currencyColor = colors.palette.primary200 const {unitBalance} = props - // const headerBg = getMintColor(unitBalance.unit) - - // log.trace('[UnitBalanceBlock]', {unitBalance}) return ( - + <> - + + + ) }) @@ -700,10 +810,64 @@ const PromoBlock = function (props: {addMint: any}) { } -const MintsByUnitListItem = observer(function (props: { +const MintsByUnitSummary = observer(function (props: { mintsByUnit: MintsByUnit navigation: any }) { + const { + mintsStore, + proofsStore, + } = useStores() + + const [selectedMint, setSelectedMint] = useState(undefined) + + useEffect(() => { + const setSelected = async () => { + const balance = proofsStore.getMintBalanceWithMaxBalance(props.mintsByUnit.unit) + if(!balance) return + + const mint = mintsStore.findByUrl(balance?.mintUrl) + setSelectedMint(mint) + } + + setSelected() + return () => {} + }, []) + + const {mintsByUnit} = props + const mintsCountText = `and ${mintsByUnit.mints.length} other${mintsByUnit.mints.length > 1 && 's'}` + + return ( + + + + + 1 ? mintsCountText : ''}`}/> + + ) +}) + + +const MintsByUnitList = observer(function (props: { + mintsByUnit: MintsByUnit[] + currentUnit: MintUnit + onTopup: Function + onLightningPay: Function + onMintInfo: Function +}) { const [selectedMintUrl, setSelectedMintUrl] = useState(undefined) @@ -717,153 +881,113 @@ const MintsByUnitListItem = observer(function (props: { LayoutAnimation.easeInEaseOut() setSelectedMintUrl(mintUrl) } - - const gotoMintInfo = function (mintUrl: string) { - props.navigation.navigate('SettingsNavigator', {screen: 'MintInfo', params: {mintUrl}, initial: false}) - } - - - const gotoTopup = function (unit: MintUnit, mintUrl: string) { - props.navigation.navigate('Topup', { - mintUrl, - unit - }) - } - - - const gotoLightningPay = async function (unit: MintUnit, mintUrl: string, ) { - props.navigation.navigate('LightningPay', { - mintUrl, - unit - }) - } - - - const gotoSend = async function (unit: MintUnit, mintUrl: string) { - props.navigation.navigate('Send', { - mintUrl, - unit - }) - } - - + const color = useThemeColor('textDim') const balanceColor = useThemeColor('amount') const {mintsByUnit} = props - - - - /* const isSingleMint: boolean = mintsByUnit.mints.length === 1 || false - const singleMint: Mint = mintsByUnit.mints[0] */ - + const mintsByUnitCurrent = mintsByUnit.find((mintUnit) => mintUnit.unit === props.currentUnit) return ( - - {mintsByUnit.mints.map((mint: Mint) => ( - - + <> + {mintsByUnitCurrent!.mints.map((mint: Mint, index: number) => ( + + + + + } + RightComponent={ + + } + //topSeparator={true} + style={$item} + onPress={() => onSelectedMint(mint.mintUrl)} + topSeparator={index !== 0 ?? true} + /> + {selectedMintUrl === mint.mintUrl && ( + +