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

Swipe to reply send #1016

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion apps/expo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"react-dom": "^18.2.0",
"react-native": "0.74.1",
"react-native-dotenv": "^3.4.9",
"react-native-gesture-handler": "~2.14.1",
"react-native-gesture-handler": "^2.22.0",
"react-native-ios-modal": "^0.1.8",
"react-native-reanimated": "~3.10.1",
"react-native-safe-area-context": "4.8.2",
Expand Down
9 changes: 6 additions & 3 deletions apps/next/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Provider } from 'app/provider'
import { YStack, H1, H2 } from '@my/ui'
import { IconSendLogo } from 'app/components/icons'
import { SendV0TokenUpgradeScreen } from 'app/features/send-token-upgrade/screen'
import { GestureHandlerRootView } from 'react-native-gesture-handler'

if (process.env.NODE_ENV === 'production') {
require('../public/tamagui.css')
Expand Down Expand Up @@ -65,9 +66,11 @@ function MyApp({
<Provider initialSession={pageProps.initialSession}>
{/* TODO: create a concerns screen or move to provider instead of wrapping here in next app */}
<MaintenanceMode>
<SendV0TokenUpgradeScreen>
{getLayout(<Component {...pageProps} />)}
</SendV0TokenUpgradeScreen>
<GestureHandlerRootView style={{ flex: 1 }}>
<SendV0TokenUpgradeScreen>
{getLayout(<Component {...pageProps} />)}
</SendV0TokenUpgradeScreen>
</GestureHandlerRootView>
</MaintenanceMode>
</Provider>
</NextThemeProvider>
Expand Down
11 changes: 8 additions & 3 deletions packages/app/components/sidebar/HomeSideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { NavSheet } from '../NavSheet'
import { useUser } from 'app/utils/useUser'
import { ReferralLink } from '../ReferralLink'
import { useHoverStyles } from 'app/utils/useHoverStyles'
import { useIsSendingUnlocked } from 'app/utils/useIsSendingUnlocked'

const links = [
{
Expand Down Expand Up @@ -60,16 +61,20 @@ const links = [
].filter(Boolean) as { icon: ReactElement; text: string; href: string }[]

const HomeSideBar = ({ ...props }: YStackProps) => {
const isSendingUnlocked = useIsSendingUnlocked()

return (
<SideBar {...props} ai={'flex-start'} pl="$7">
<Link href={'/'}>
<IconSendLogo color={'$color12'} size={'$2.5'} />
</Link>

<YStack gap={'$7'} pt={'$10'} jc={'space-between'}>
{links.map((link) => (
<SideBarNavLink key={link.href} {...link} />
))}
{links
.filter((link) => (isSendingUnlocked ? true : link.href !== '/send'))
.map((link) => (
<SideBarNavLink key={link.href} {...link} />
))}
</YStack>
</SideBar>
)
Expand Down
13 changes: 10 additions & 3 deletions packages/app/features/activity/RecentActivity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,19 @@ function ActivityFeed({
<YStack key={date} gap={'$3.5'}>
<RowLabel>{date}</RowLabel>
<Fade>
<YGroup bc={'$color1'} p={'$2'} $gtLg={{ p: '$3.5' }}>
{activities.map((activity) => (
<YGroup bc={'$color0'} $gtLg={{ p: '$3.5' }}>
{activities.map((activity, activityIndex) => (
<YGroup.Item
key={`${activity.event_name}-${activity.created_at}-${activity?.from_user?.id}-${activity?.to_user?.id}`}
>
<TokenActivityRow activity={activity} onPress={onActivityPress} />
<TokenActivityRow
activity={activity}
onPress={() => onActivityPress(activity)}
btrr={activityIndex === 0 ? '$4' : 0}
btlr={activityIndex === 0 ? '$4' : 0}
bbrr={activityIndex === activities.length - 1 ? '$4' : 0}
bblr={activityIndex === activities.length - 1 ? '$4' : 0}
/>
</YGroup.Item>
))}
</YGroup>
Expand Down
233 changes: 160 additions & 73 deletions packages/app/features/home/TokenActivityRow.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Paragraph, Text, XStack, YStack } from '@my/ui'
import { Paragraph, Stack, Text, XStack, type XStackProps, YStack, useIsTouchDevice } from '@my/ui'
import { amountFromActivity, eventNameFromActivity, subtextFromActivity } from 'app/utils/activity'
import {
isSendAccountReceiveEvent,
Expand All @@ -8,104 +8,191 @@ import {
import { ActivityAvatar } from '../activity/ActivityAvatar'
import { CommentsTime } from 'app/utils/dateHelper'
import { Link } from 'solito/link'
import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable'
import type { SharedValue } from 'react-native-reanimated'

import { useUser } from 'app/utils/useUser'
import { useHoverStyles } from 'app/utils/useHoverStyles'
import { useRouter } from 'solito/router'
import { Reply } from '@tamagui/lucide-icons'
import { useState } from 'react'

const ReplySendAction = ({
recipient,
idType,
progress,
}: { recipient: string | undefined; idType: string; progress: SharedValue<number> }) => {
const router = useRouter()
const [scale, setScale] = useState(0.0)
const [opacity, setOpacity] = useState(0.0)
//Needs a number id to avoid conflicts with other progress listeners
progress.addListener(0, (value) => {
setScale(Math.min(value, 1.0))
setOpacity(Math.min(value, 1.0))
})

const onPress = () => {
router.push({
pathname: '/send',
query: { recipient: recipient, idType: idType },
})
}
return (
<Stack jc="center" ai="flex-end" pl="$4" pr={'$1'} onPress={onPress} bc="$color0">
<Reply
color="$primary"
$theme-light={{ color: '$color12' }}
animation="200ms"
scale={scale}
opacity={opacity}
/>
</Stack>
)
}

export function TokenActivityRow({
activity,
onPress,
...props
}: {
activity: Activity
onPress?: (activity: Activity) => void
}) {
} & XStackProps) {
const { profile } = useUser()
const { created_at, from_user, to_user } = activity
const router = useRouter()
const amount = amountFromActivity(activity)
const date = CommentsTime(new Date(created_at))
const eventName = eventNameFromActivity(activity)
const subtext = subtextFromActivity(activity)
const isERC20Transfer = isSendAccountTransfersEvent(activity)
const isETHReceive = isSendAccountReceiveEvent(activity)
const hoverStyles = useHoverStyles()
const isTouchDevice = useIsTouchDevice()
const [isSwipeTriggered, setIsSwipeTriggered] = useState(false)
const replyRecipient =
profile?.send_id === from_user?.send_id ? to_user?.send_id : from_user?.send_id

const renderRightActions = (progress: SharedValue<number>) => {
//Needs a number id to avoid conflicts with other progress listeners
progress.addListener(1, (value) => {
if (value > 1.1) {
setIsSwipeTriggered(true)
} else {
setIsSwipeTriggered(false)
}
})

return (
<ReplySendAction
recipient={replyRecipient?.toString()}
idType={'sendid'}
progress={progress}
/>
)
}

const handleOnSwipeableWillClose = () => {
if (isSwipeTriggered) {
router.push({
pathname: '/send',
query: { recipient: replyRecipient, idType: 'sendid' },
})
}
}

return (
<XStack
width={'100%'}
ai="center"
jc="space-between"
gap="$4"
p="$3.5"
br={'$4'}
cursor={onPress ? 'pointer' : 'default'}
$gtLg={{ p: '$5' }}
testID={'TokenActivityRow'}
hoverStyle={onPress ? hoverStyles : null}
onPress={() => onPress?.(activity)}
<Swipeable
enabled={isTouchDevice && !!replyRecipient}
renderRightActions={renderRightActions}
rightThreshold={40}
friction={2}
overshootFriction={8}
containerStyle={{ backgroundColor: 'transparent' }}
hitSlop={{ right: -20 }}
onSwipeableWillClose={handleOnSwipeableWillClose}
>
<XStack gap="$3.5" width={'100%'} f={1}>
<ActivityAvatar activity={activity} />
<YStack width={'100%'} f={1} overflow="hidden">
<XStack fd="row" jc="space-between" gap="$1.5" f={1} width={'100%'}>
<Text color="$color12" fontSize="$6" fontWeight={'500'}>
{eventName}
</Text>
<Text color="$color12" fontSize="$6" fontWeight={'500'} ta="right">
{amount}
</Text>
</XStack>
<XStack
gap="$1.5"
alignItems="flex-start"
justifyContent="space-between"
width="100%"
overflow="hidden"
f={1}
>
{(isERC20Transfer || isETHReceive) &&
Boolean(to_user?.send_id) &&
Boolean(from_user?.send_id) ? (
<XStack
onPress={(e) => {
e.stopPropagation()
}}
maxWidth={'60%'}
>
<Link
href={`/profile/${
profile?.send_id === from_user?.send_id ? to_user?.send_id : from_user?.send_id
}`}
viewProps={{
style: { maxWidth: '100%' },
<XStack
width={'100%'}
ai="center"
jc="space-between"
gap="$4"
p="$5"
br={'$4'}
cursor={onPress ? 'pointer' : 'default'}
$gtLg={{ p: '$5' }}
testID={'TokenActivityRow'}
hoverStyle={onPress ? hoverStyles : null}
onPress={() => onPress?.(activity)}
bc="$color1"
{...props}
>
<XStack gap="$3.5" width={'100%'} f={1}>
<ActivityAvatar activity={activity} />
<YStack width={'100%'} f={1} overflow="hidden">
<XStack fd="row" jc="space-between" gap="$1.5" f={1} width={'100%'}>
<Text color="$color12" fontSize="$6" fontWeight={'500'}>
{eventName}
</Text>
<Text color="$color12" fontSize="$6" fontWeight={'500'} ta="right">
{amount}
</Text>
</XStack>
<XStack
gap="$1.5"
alignItems="flex-start"
justifyContent="space-between"
width="100%"
overflow="hidden"
f={1}
>
{(isERC20Transfer || isETHReceive) &&
Boolean(to_user?.send_id) &&
Boolean(from_user?.send_id) ? (
<XStack
onPress={(e) => {
e.stopPropagation()
}}
maxWidth={'60%'}
>
<Paragraph
color="$color10"
fontFamily={'$mono'}
fontSize="$5"
textDecorationLine="underline"
<Link
href={`/profile/${
profile?.send_id === from_user?.send_id
? to_user?.send_id
: from_user?.send_id
}`}
viewProps={{
style: { maxWidth: '100%' },
}}
>
{subtext}
</Paragraph>
</Link>
</XStack>
) : (
<Paragraph
color="$color10"
fontFamily={'$mono'}
maxWidth={'100%'}
overflow={'hidden'}
fontSize="$5"
>
{subtext}
<Paragraph
color="$color10"
fontFamily={'$mono'}
fontSize="$5"
textDecorationLine="underline"
>
{subtext}
</Paragraph>
</Link>
</XStack>
) : (
<Paragraph
color="$color10"
fontFamily={'$mono'}
maxWidth={'100%'}
overflow={'hidden'}
fontSize="$5"
>
{subtext}
</Paragraph>
)}
<Paragraph color="$color10" size={'$5'} textAlign={'right'}>
{date}
</Paragraph>
)}
<Paragraph color="$color10" size={'$5'} textAlign={'right'}>
{date}
</Paragraph>
</XStack>
</YStack>
</XStack>
</YStack>
</XStack>
</XStack>
</XStack>
</Swipeable>
)
}
2 changes: 1 addition & 1 deletion packages/app/features/home/TokenDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export const TokenDetailsMarketData = ({ coin }: { coin: CoinWithBalance }) => {
<Spinner size="small" color={'$color12'} />
) : (
<XStack gap={'$1.5'} ai="center" jc={'space-around'}>
{changePercent24h === undefined ? (
{!changePercent24h ? (
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using !changePercent24h will evaluate to true when changePercent24h is 0, which is a valid percentage change value that should be displayed. The original check for changePercent24h === undefined was correct since it specifically tests for the loading/error state.

Spotted by Graphite Reviewer

Is this helpful? React 👍 or 👎 to let us know.

<XStack gap="$2" ai="center">
<Paragraph color="$color10">Failed to load market data</Paragraph>
<IconError size="$1.75" color={'$redVibrant'} />
Expand Down
Loading
Loading