Skip to content

Commit

Permalink
feat(mobile): [Budget] animate budget list
Browse files Browse the repository at this point in the history
  • Loading branch information
Quốc Khánh authored and bkdev98 committed Jul 20, 2024
1 parent 5a2c1db commit 2501d04
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 16 deletions.
143 changes: 132 additions & 11 deletions apps/mobile/app/(app)/(tabs)/budgets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,122 @@ import { BudgetStatistic } from '@/components/budget/budget-statistic'
import { BurndownChart } from '@/components/budget/burndown-chart'
import { Toolbar } from '@/components/common/toolbar'
import { Skeleton } from '@/components/ui/skeleton'
import { Text } from '@/components/ui/text'
import { useColorScheme } from '@/hooks/useColorScheme'
import { theme } from '@/lib/theme'
import { useBudgetList } from '@/stores/budget/hooks'
import type { BudgetPopulated } from '@6pm/validation'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { LinearGradient } from 'expo-linear-gradient'
import { SectionList, Text, View } from 'react-native'
import { Dimensions, SectionList, View } from 'react-native'
import Animated, {
Extrapolation,
interpolate,
useAnimatedScrollHandler,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated'
import { useSafeAreaInsets } from 'react-native-safe-area-context'

const { height } = Dimensions.get('screen')

const chartHeight = 187
const spacing = 16

const AnimatedSectionList = Animated.createAnimatedComponent(
SectionList<BudgetPopulated>,
)

export default function BudgetsScreen() {
const { i18n } = useLingui()
const { bottom } = useSafeAreaInsets()
const { colorScheme } = useColorScheme()
const headerAnimation = useSharedValue(0)
const scrollY = useSharedValue(0)
const headerHeight = useSharedValue(height)

const dummyHeaderStyle = useAnimatedStyle(() => {
return {
height: headerHeight.value,
}
})

const summaryStyle = useAnimatedStyle(() => {
const extraSectionHeaderSpacing = spacing * 2
return {
opacity: interpolate(
headerAnimation.value,
[
0,
chartHeight,
chartHeight + extraSectionHeaderSpacing,
headerHeight.value,
],
[1, 1, 1, 0],
),
transform: [
{
translateY: interpolate(
headerAnimation.value,
[
0,
chartHeight,
chartHeight + extraSectionHeaderSpacing,
chartHeight + extraSectionHeaderSpacing + 1,
],
[0, 0, 0, -1],
),
},
],
}
})

const chartStyle = useAnimatedStyle(() => ({
transform: [
{
perspective: chartHeight * 5,
},
{
translateY: interpolate(
headerAnimation.value,
[0, chartHeight],
[0, -chartHeight / 2 - spacing],
Extrapolation.EXTEND,
),
},
{
rotateX: `${interpolate(
headerAnimation.value,
[0, chartHeight],
[0, 90],
Extrapolation.CLAMP,
)}deg`,
},
{
scaleY: interpolate(
headerAnimation.value,
[-chartHeight, 0],
[1.4, 1],
Extrapolation.CLAMP,
),
},
],
opacity: interpolate(
headerAnimation.value,
[0, chartHeight / 2, chartHeight],
[1, 0.6, 0],
Extrapolation.CLAMP,
),
}))

const onScroll = useAnimatedScrollHandler((event) => {
const { y } = event.contentOffset
scrollY.value = y
headerAnimation.value = y
})

const {
spendingBudgets,
savingBudgets,
Expand All @@ -35,23 +138,41 @@ export default function BudgetsScreen() {

return (
<View className="flex-1 bg-card">
<SectionList
ListHeaderComponent={
<View className="gap-6 pb-4 pt-8">
<BudgetStatistic totalRemaining={1000} remainingPerDay={100} />
<BurndownChart />
</View>
}
className="bg-card flex-1"
contentContainerClassName="px-6"
<View
className="absolute w-full"
onLayout={(ev) => {
if (headerHeight.value === ev.nativeEvent.layout.height) {
return
}
headerHeight.value = withTiming(ev.nativeEvent.layout.height, {
duration: 0,
})
}}
>
<Animated.View className="gap-6 py-6 px-6" style={summaryStyle}>
<BudgetStatistic totalRemaining={1000} remainingPerDay={100} />
</Animated.View>
<Animated.View
className="px-6 pb-5"
style={[{ flexGrow: 0 }, chartStyle]}
>
<BurndownChart />
</Animated.View>
</View>
<AnimatedSectionList
onScroll={onScroll}
showsVerticalScrollIndicator={false}
ListHeaderComponent={<Animated.View style={dummyHeaderStyle} />}
contentContainerStyle={{ paddingBottom: bottom + 32 }}
refreshing={isRefetching}
onRefresh={refetch}
sections={sections}
keyExtractor={(item) => item.id}
renderItem={({ item: budget }) => <BudgetItem budget={budget} />}
renderSectionHeader={({ section: { title } }) => (
<Text className="text-muted-foreground bg-card py-2">{title}</Text>
<Text className="text-muted-foreground bg-card py-2 px-6">
{title}
</Text>
)}
ListFooterComponent={
(isLoading || isRefetching) && !sections.length ? (
Expand Down
4 changes: 2 additions & 2 deletions apps/mobile/components/budget/budget-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const BudgetItem: FC<BudgetItemProps> = ({ budget }) => {

const amountPerDay = 312500

const usagePercentage = 80
const usagePercentage = Math.random() * 100

const remainingDays = useMemo(() => {
let periodEndDate: Date | null
Expand Down Expand Up @@ -69,7 +69,7 @@ export const BudgetItem: FC<BudgetItemProps> = ({ budget }) => {
params: { budgetId: budget.id },
}}
>
<Pressable className="gap-4 mb-3 mt-1 justify-between p-4 border border-border rounded-lg">
<Pressable className="gap-4 mx-6 mb-3 mt-1 justify-between p-4 border border-border rounded-lg">
<View className="flex-row items-center gap-6 justify-between">
<View className="gap-2">
<Text
Expand Down
4 changes: 2 additions & 2 deletions apps/mobile/components/budget/budget-statistic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function BudgetStatistic({
{totalRemaining?.toLocaleString() || '0.00'}{' '}
<Text className="text-muted-foreground font-normal text-sm">VND</Text>
</Text>
<Text className="text-muted-foreground text-sm">
<Text className="text-muted-foreground">
{t(i18n)`Left this month`}
</Text>
</View>
Expand All @@ -30,7 +30,7 @@ export function BudgetStatistic({
{remainingPerDay?.toLocaleString() || '0.00'}{' '}
<Text className="text-muted-foreground font-normal text-sm">VND</Text>
</Text>
<Text className="text-muted-foreground text-sm text-right">
<Text className="text-muted-foreground text-right">
{t(i18n)`Left per day`}
</Text>
</View>
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/components/common/circular-progress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export function CircularProgress({
},
]}
>
<Text className="text-sm font-semibold">{progress}%</Text>
<Text className="text-sm font-semibold">{Math.round(progress)}%</Text>
</View>
</View>
)
Expand Down

0 comments on commit 2501d04

Please sign in to comment.