Skip to content

Commit

Permalink
add dashboard transition to qr screen
Browse files Browse the repository at this point in the history
  • Loading branch information
KKA11010 committed Dec 6, 2023
1 parent 9e8ee86 commit 4bedf04
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 87 deletions.
85 changes: 85 additions & 0 deletions src/components/animation/QrTransition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import type { RootStackParamList } from '@model/nav'
import { type NavigationProp,useNavigation } from '@react-navigation/core'
import { useThemeContext } from '@src/context/Theme'
import { useRef } from 'react'
import { Animated, Easing } from 'react-native'

type StackNavigation = NavigationProp<RootStackParamList>

export const useTransitionAnimation = () => {
const nav = useNavigation<StackNavigation>()
const { color } = useThemeContext()
const animatedColorValue = useRef(new Animated.Value(0)).current
const animatedPositionValue = useRef(new Animated.Value(0)).current
const animatedOpacityValue = useRef(new Animated.Value(0)).current
const animatedMarginValue = useRef(new Animated.Value(0)).current
const animationEnded = useRef(false)
const interpolatedColor = animatedColorValue.interpolate({
inputRange: animationEnded.current ? [1, 0] : [0, 1],
outputRange: animationEnded.current ? ['#000', color.BACKGROUND] : [color.BACKGROUND, '#000'],
})
const interpolatedPosition = animatedPositionValue.interpolate({
inputRange: animationEnded.current ? [1, 0] : [0, 1],
outputRange: animationEnded.current ? [100, 0] : [0, 100],
})
const interpolatedOpacity = animatedOpacityValue.interpolate({
inputRange: animationEnded.current ? [1, 0] : [0, 1],
outputRange: animationEnded.current ? [0, 1] : [1, 0],
})
const interpolatedMargin = animatedMarginValue.interpolate({
inputRange: animationEnded.current ? [1, 0] : [0, 1],
outputRange: animationEnded.current ? [-1000, 0] : [0, -1000],
})
const animatedBgStyles = {
backgroundColor: interpolatedColor,
}
const animatedPosStyles = {
transform: [{ translateY: interpolatedPosition }],
}
const animatedOpacityStyles = {
opacity: interpolatedOpacity,
}
const animatedMarginStyles = {
marginTop: interpolatedMargin,
}
const animateTransition = () => {
Animated.parallel([
Animated.timing(animatedColorValue, {
toValue: animationEnded.current ? 0 : 1,
duration: 300,
easing: Easing.linear,
useNativeDriver: false,
}),
Animated.timing(animatedPositionValue, {
toValue: animationEnded.current ? 0 : 1,
duration: 300,
easing: Easing.linear,
useNativeDriver: false,
}),
Animated.timing(animatedOpacityValue, {
toValue: animationEnded.current ? 0 : 1,
duration: 150,
easing: Easing.linear,
useNativeDriver: false,
}),
Animated.timing(animatedMarginValue, {
toValue: animationEnded.current ? 0 : 1,
duration: 300,
easing: Easing.linear,
useNativeDriver: false,
})
]).start(() => {
if (animationEnded.current) { return animationEnded.current = false }
nav.navigate('qr scan', { mint: undefined })
animationEnded.current = true
})
}
return {
animatedBgStyles,
animatedPosStyles,
animatedOpacityStyles,
animatedMarginStyles,
animationEnded,
animateTransition,
}
}
112 changes: 65 additions & 47 deletions src/components/nav/BottomNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,20 @@ import { STORE_KEYS } from '@store/consts'
import { highlight as hi } from '@styles'
import { isStr } from '@util'
import { useTranslation } from 'react-i18next'
import { SafeAreaView, TouchableOpacity } from 'react-native'
import { Animated, SafeAreaView, TouchableOpacity } from 'react-native'
import { s, ScaledSheet, vs } from 'react-native-size-matters'

export default function BottomNav({ navigation, route }: TBottomNavProps) {
type TInterPolation = Animated.AnimatedInterpolation<string | number>

export default function BottomNav({
navigation,
route,
animatedBgStyles,
animatedPosStyles
}: TBottomNavProps & {
animatedBgStyles: { backgroundColor: TInterPolation },
animatedPosStyles: { transform: { translateY: TInterPolation }[] }
}) {
const { t } = useTranslation([NS.topNav])
const { color, highlight } = useThemeContext()

Expand All @@ -37,51 +47,59 @@ export default function BottomNav({ navigation, route }: TBottomNavProps) {
route.name === 'Contacts settings'

return (
<SafeAreaView style={[styles.bottomNav, { backgroundColor: color.BACKGROUND, paddingBottom: vs(10) }]}>
<TouchableOpacity
style={styles.navIcon}
onPress={() => void handleNav('dashboard')}
disabled={isWalletRelatedScreen}
>
<WalletIcon width={s(26)} height={s(26)} color={isWalletRelatedScreen ? hi[highlight] : color.TEXT} />
<Txt
txt={t('wallet', { ns: NS.bottomNav })}
styles={[styles.iconTxt, {
color: isWalletRelatedScreen ? hi[highlight] : color.TEXT,
fontWeight: isWalletRelatedScreen ? '500' : '400'
}]}
/>
</TouchableOpacity>
<TouchableOpacity
style={styles.navIcon}
onPress={() => void handleNav('Address book')}
disabled={route.name === 'Address book'}
>
<BookIcon width={s(26)} height={s(26)} color={route.name === 'Address book' ? hi[highlight] : color.TEXT} />
<Txt
txt={t('contacts', { ns: NS.bottomNav })}
styles={[
styles.iconTxt, {
color: route.name === 'Address book' ? hi[highlight] : color.TEXT,
fontWeight: route.name === 'Address book' ? '500' : '400'
}
]}
/>
</TouchableOpacity>
<TouchableOpacity
style={styles.navIcon}
onPress={() => void handleNav('Settings')}
disabled={isSettingsRelatedScreen}
>
<SettingsIcon width={s(26)} height={s(26)} color={isSettingsRelatedScreen ? hi[highlight] : color.TEXT} />
<Txt
txt={t('settings')}
styles={[styles.iconTxt, {
color: isSettingsRelatedScreen ? hi[highlight] : color.TEXT,
fontWeight: isSettingsRelatedScreen ? '500' : '400'
}]}
/>
</TouchableOpacity>
<SafeAreaView>
<Animated.View
style={[
styles.bottomNav,
{ paddingBottom: vs(10) },
animatedBgStyles,
animatedPosStyles
]}>
<TouchableOpacity
style={styles.navIcon}
onPress={() => void handleNav('dashboard')}
disabled={isWalletRelatedScreen}
>
<WalletIcon width={s(26)} height={s(26)} color={isWalletRelatedScreen ? hi[highlight] : color.TEXT} />
<Txt
txt={t('wallet', { ns: NS.bottomNav })}
styles={[styles.iconTxt, {
color: isWalletRelatedScreen ? hi[highlight] : color.TEXT,
fontWeight: isWalletRelatedScreen ? '500' : '400'
}]}
/>
</TouchableOpacity>
<TouchableOpacity
style={styles.navIcon}
onPress={() => void handleNav('Address book')}
disabled={route.name === 'Address book'}
>
<BookIcon width={s(26)} height={s(26)} color={route.name === 'Address book' ? hi[highlight] : color.TEXT} />
<Txt
txt={t('contacts', { ns: NS.bottomNav })}
styles={[
styles.iconTxt, {
color: route.name === 'Address book' ? hi[highlight] : color.TEXT,
fontWeight: route.name === 'Address book' ? '500' : '400'
}
]}
/>
</TouchableOpacity>
<TouchableOpacity
style={styles.navIcon}
onPress={() => void handleNav('Settings')}
disabled={isSettingsRelatedScreen}
>
<SettingsIcon width={s(26)} height={s(26)} color={isSettingsRelatedScreen ? hi[highlight] : color.TEXT} />
<Txt
txt={t('settings')}
styles={[styles.iconTxt, {
color: isSettingsRelatedScreen ? hi[highlight] : color.TEXT,
fontWeight: isSettingsRelatedScreen ? '500' : '400'
}]}
/>
</TouchableOpacity>
</Animated.View>
</SafeAreaView>
)
}
Expand Down
104 changes: 64 additions & 40 deletions src/screens/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
import { useTransitionAnimation } from '@comps/animation/QrTransition'
import Balance from '@comps/Balance'
import { IconBtn } from '@comps/Button'
import useLoading from '@comps/hooks/Loading'
Expand Down Expand Up @@ -31,15 +32,24 @@ import { claimToken, getMintsForPayment } from '@wallet'
import { getTokenInfo } from '@wallet/proofs'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { TouchableOpacity, View } from 'react-native'
import { Animated, TouchableOpacity, View } from 'react-native'
import { s, ScaledSheet, vs } from 'react-native-size-matters'

export default function Dashboard({ navigation, route }: TDashboardPageProps) {
const { t } = useTranslation([NS.common])
// qr screen transition
const {
animatedBgStyles,
animatedPosStyles,
animatedOpacityStyles,
animatedMarginStyles,
animationEnded,
animateTransition,
} = useTransitionAnimation()
// The URL content that redirects to this app after clicking on it (cashu:)
const { url, clearUrl } = useInitialURL()
// Theme
const { color, highlight } = useThemeContext()
const { highlight } = useThemeContext()
// State to indicate token claim from clipboard after app comes to the foreground, to re-render total balance
const { claimed } = useFocusClaimContext()
// Nostr
Expand Down Expand Up @@ -295,6 +305,12 @@ export default function Dashboard({ navigation, route }: TDashboardPageProps) {
// get balance after navigating to this page
useEffect(() => {
const focusHandler = navigation.addListener('focus', async () => {
if (animationEnded.current) {
const t = setTimeout(() => {
animateTransition()
clearTimeout(t)
}, 200)
}
const data = await Promise.all([
getBalance(),
hasMints()
Expand All @@ -303,6 +319,7 @@ export default function Dashboard({ navigation, route }: TDashboardPageProps) {
setHasMint(data[1])
})
return focusHandler
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [navigation])

// prevent back navigation - https://reactnavigation.org/docs/preventing-going-back/
Expand All @@ -313,48 +330,50 @@ export default function Dashboard({ navigation, route }: TDashboardPageProps) {
}, [navigation])

return (
<View style={[styles.container, { backgroundColor: color.BACKGROUND }]}>
{/* Balance, Disclaimer & History */}
<Balance balance={balance} nav={navigation} />
{/* Receive/send/mints buttons */}
<View style={[styles.actionWrap, { paddingHorizontal: s(20) }]}>
{/* Send button or add first mint */}
{hasMint ?
<Animated.View style={[styles.container, animatedBgStyles]}>
<Animated.View style={animatedMarginStyles}>
{/* Balance, Disclaimer & History */}
<Balance balance={balance} nav={navigation} />
{/* Receive/send/mints buttons */}
<View style={[styles.actionWrap, { paddingHorizontal: s(20) }]}>
{/* Send button or add first mint */}
{hasMint ?
<ActionBtn
icon={<SendIcon width={s(32)} height={vs(32)} color={hi[highlight]} />}
txt={t('send', { ns: NS.wallet })}
color={hi[highlight]}
onPress={() => setModal(prev => ({ ...prev, sendOpts: true }))}
/>
:
<ActionBtn
icon={<PlusIcon width={s(36)} height={vs(36)} color={hi[highlight]} />}
txt='Mint'
color={hi[highlight]}
onPress={() => setModal(prev => ({ ...prev, mint: true }))}
/>
}
<ActionBtn
icon={<SendIcon width={s(32)} height={vs(32)} color={hi[highlight]} />}
txt={t('send', { ns: NS.wallet })}
icon={<ScanQRIcon width={s(32)} height={vs(32)} color={hi[highlight]} />}
txt={t('scan')}
color={hi[highlight]}
onPress={() => setModal(prev => ({ ...prev, sendOpts: true }))}
onPress={() => animateTransition()}
/>
:
<ActionBtn
icon={<PlusIcon width={s(36)} height={vs(36)} color={hi[highlight]} />}
txt='Mint'
icon={<ReceiveIcon width={s(32)} height={vs(32)} color={hi[highlight]} />}
txt={t('receive', { ns: NS.wallet })}
color={hi[highlight]}
onPress={() => setModal(prev => ({ ...prev, mint: true }))}
onPress={() => {
if (!hasMint) {
// try to claim from clipboard to avoid receive-options-modal to popup and having to press again
return handleClaimBtnPress()
}
setModal(prev => ({ ...prev, receiveOpts: true }))
}}
/>
}
<ActionBtn
icon={<ScanQRIcon width={s(32)} height={vs(32)} color={hi[highlight]} />}
txt={t('scan')}
color={hi[highlight]}
onPress={() => navigation.navigate('qr scan', { mint: undefined })}
/>
<ActionBtn
icon={<ReceiveIcon width={s(32)} height={vs(32)} color={hi[highlight]} />}
txt={t('receive', { ns: NS.wallet })}
color={hi[highlight]}
onPress={() => {
if (!hasMint) {
// try to claim from clipboard to avoid receive-options-modal to popup and having to press again
return handleClaimBtnPress()
}
setModal(prev => ({ ...prev, receiveOpts: true }))
}}
/>
</View>
</View>
</Animated.View>
{/* beta warning */}
<View style={styles.hintWrap}>
<Animated.View style={[styles.hintWrap, animatedOpacityStyles]}>
<TouchableOpacity
onPress={() => navigation.navigate('disclaimer')}
style={styles.betaHint}
Expand All @@ -363,9 +382,14 @@ export default function Dashboard({ navigation, route }: TDashboardPageProps) {
<Txt txt={t('enutsBeta')} styles={[{ color: mainColors.WARN, marginHorizontal: s(10) }]} />
<ChevronRightIcon width={s(10)} height={vs(16)} color={mainColors.WARN} />
</TouchableOpacity>
</View>
</Animated.View>
{/* Bottom nav icons */}
<BottomNav navigation={navigation} route={route} />
<BottomNav
navigation={navigation}
route={route}
animatedBgStyles={animatedBgStyles}
animatedPosStyles={animatedPosStyles}
/>
{/* Question modal for mint trusting */}
{trustModal &&
<TrustMintModal
Expand Down Expand Up @@ -425,7 +449,7 @@ export default function Dashboard({ navigation, route }: TDashboardPageProps) {
void store.set(STORE_KEYS.nostrReseted, '1')
}}
/>
</View>
</Animated.View>
)
}

Expand Down

0 comments on commit 4bedf04

Please sign in to comment.