Skip to content

Commit

Permalink
feat(mobile): animate tab bar and minor theme adjustments (#272)
Browse files Browse the repository at this point in the history
  • Loading branch information
bkdev98 authored Sep 4, 2024
1 parent b610b09 commit c14922a
Show file tree
Hide file tree
Showing 15 changed files with 168 additions and 108 deletions.
5 changes: 4 additions & 1 deletion apps/mobile/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ web-build/
# The following patterns were generated by expo-cli

expo-env.d.ts
# @end expo-cli
# @end expo-cli

# build artifacts
*.tar.gz
71 changes: 4 additions & 67 deletions apps/mobile/app/(app)/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import { TabBar } from '@/components/common/tab-bar'
import { Button } from '@/components/ui/button'
import { Text } from '@/components/ui/text'
import { useColorScheme } from '@/hooks/useColorScheme'
import { theme } from '@/lib/theme'
import { cn } from '@/lib/utils'
import { useUser } from '@clerk/clerk-expo'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import * as Haptics from 'expo-haptics'
import { Link, Tabs } from 'expo-router'
import {
// BarChartBigIcon,
CogIcon,
LandPlotIcon,
PlusIcon,
WalletIcon,
} from 'lucide-react-native'
import { PlusIcon } from 'lucide-react-native'
import { usePostHog } from 'posthog-react-native'
import { useEffect } from 'react'
import { View, useWindowDimensions } from 'react-native'
import { useWindowDimensions } from 'react-native'
import Purchases from 'react-native-purchases'
import { useSafeAreaInsets } from 'react-native-safe-area-context'

Expand All @@ -43,6 +36,7 @@ export default function TabLayout() {

return (
<Tabs
tabBar={(props) => <TabBar {...props} />}
screenOptions={{
headerShadowVisible: false,
tabBarActiveTintColor: theme[colorScheme ?? 'light'].background,
Expand Down Expand Up @@ -75,17 +69,6 @@ export default function TabLayout() {
name="index"
options={{
headerShown: false,
tabBarShowLabel: false,
tabBarIcon: ({ color, focused }) => (
<View
className={cn(
'h-[48px] w-[48px] items-center justify-center rounded-xl',
focused && 'bg-primary',
)}
>
<WalletIcon color={color} className="size-6" />
</View>
),
}}
/>
<Tabs.Screen
Expand All @@ -98,17 +81,6 @@ export default function TabLayout() {
color: theme[colorScheme ?? 'light'].primary,
marginLeft: 5,
},
tabBarShowLabel: false,
tabBarIcon: ({ color, focused }) => (
<View
className={cn(
'h-[48px] w-[48px] items-center justify-center rounded-xl',
focused && 'bg-primary',
)}
>
<LandPlotIcon color={color} className="size-6" />
</View>
),
headerRight: () => (
<Link href="/budget/new-budget" asChild>
<Button size="sm" variant="secondary" className="mr-6 h-10">
Expand All @@ -132,41 +104,6 @@ export default function TabLayout() {
name="settings"
options={{
headerTitle: t(i18n)`Settings`,
tabBarShowLabel: false,
tabBarIcon: ({ color, focused }) => (
<View
className={cn(
'h-[48px] w-[48px] items-center justify-center rounded-xl',
focused && 'bg-primary',
)}
>
<CogIcon color={color} className="size-6" />
</View>
),
}}
/>
<Tabs.Screen
name="dummy"
options={{
tabBarShowLabel: false,
tabBarButton: () => (
<View className="items-center justify-center px-2">
<Link
href="/transaction/new-record"
asChild
onPress={Haptics.selectionAsync}
>
<Button
size="icon"
className={cn(
'h-[48px] w-[48px] items-center justify-center rounded-xl border border-border bg-muted active:bg-muted/75',
)}
>
<PlusIcon className="size-6 text-foreground" />
</Button>
</Link>
</View>
),
}}
/>
</Tabs>
Expand Down
5 changes: 4 additions & 1 deletion apps/mobile/app/(app)/(tabs)/budgets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,10 @@ export default function BudgetsScreen() {
}, 0)

const chartData = map(
groupBy(transactions, (t) => t.date),
groupBy(
transactions.filter((i) => !!i.budgetId),
(t) => t.date,
),
(transactions, key) => ({
day: new Date(key).getDate(),
amount: transactions.reduce((acc, t) => acc - t.amountInVnd, 0),
Expand Down
4 changes: 0 additions & 4 deletions apps/mobile/app/(app)/(tabs)/dummy.tsx

This file was deleted.

4 changes: 3 additions & 1 deletion apps/mobile/app/(app)/(tabs)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useColorScheme } from '@/hooks/useColorScheme'
import { formatDateShort } from '@/lib/date'
import { theme } from '@/lib/theme'
import { useTransactionList } from '@/stores/transaction/hooks'
import { useTransactionStore } from '@/stores/transaction/store'
import { dayjsExtended } from '@6pm/utilities'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
Expand Down Expand Up @@ -54,6 +55,7 @@ export default function HomeScreen() {
categoryId,
...timeRange,
})
const { draftTransactions } = useTransactionStore()

const handleSetFilter = (filter: HomeFilter) => {
if (filter === HomeFilter.ByDay) {
Expand Down Expand Up @@ -165,7 +167,7 @@ export default function HomeScreen() {
}
extraData={filter}
/>
{!transactions.length && !isLoading && (
{!transactions.length && !draftTransactions.length && !isLoading && (
<>
{filter === HomeFilter.All ? (
<View className="pointer-events-none absolute right-28 bottom-32 z-50 items-end gap-2">
Expand Down
3 changes: 3 additions & 0 deletions apps/mobile/app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ export default function AuthenticatedLayout() {
options={{
presentation: 'modal',
headerTitle: '',
headerStyle: {
backgroundColor: theme[colorScheme ?? 'light'].muted,
},
// headerShown: false,
}}
/>
Expand Down
13 changes: 5 additions & 8 deletions apps/mobile/app/(app)/paywall.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { AmountFormat } from '@/components/common/amount-format'
import { Marquee } from '@/components/common/marquee'
import { toast } from '@/components/common/toast'
import { PaywallIllustration } from '@/components/svg-assets/paywall-illustration'
Expand Down Expand Up @@ -104,13 +103,11 @@ function PackageCard({
<Text className="mb-4 text-center text-muted-foreground text-sm uppercase">
{t(i18n)`months`}
</Text>
<AmountFormat
amount={
isAnnual ? data.product.pricePerYear : data.product.pricePerMonth
}
currency="VND"
convertToDefaultCurrency
/>
<Text className="line-clamp-1 shrink-0 font-semibold text-2xl">
{isAnnual
? data.product.pricePerYearString
: data.product.pricePerMonthString}
</Text>
</View>
</Pressable>
)
Expand Down
8 changes: 4 additions & 4 deletions apps/mobile/app/(app)/transaction/new-record.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,14 @@ export default function NewRecordScreen() {
animated: true,
})
}}
className="w-[150px]"
className="w-[160px]"
>
<TabsList>
<TabsList className="border border-border">
<TabsTrigger value="0">
<KeyboardIcon className="!text-primary size-5" />
<KeyboardIcon className="!text-primary size-6" />
</TabsTrigger>
<TabsTrigger value="1">
<CameraIcon className="!text-primary size-5" />
<CameraIcon className="!text-primary size-6" />
</TabsTrigger>
</TabsList>
</Tabs>
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/components/auth/auth-local.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function AuthLocal({ onAuthenticated }: AuthLocalProps) {
}, [])

return (
<SafeAreaView className="absolute top-0 right-0 bottom-0 left-0 z-50 flex-1 items-center justify-center gap-4 bg-muted">
<SafeAreaView className="absolute top-0 right-0 bottom-0 left-0 z-50 flex-1 items-center justify-center gap-4 bg-background">
<LockKeyholeIcon className="size-12 text-primary" />
<Text className="mx-8">{t(
i18n,
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/components/common/amount-format.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const SHOULD_ROUND_VALUE_CURRENCIES = ['VND']
const amountVariants = cva('line-clamp-1 shrink-0 font-semibold', {
variants: {
size: {
xl: 'text-4xl',
xl: 'text-5xl',
lg: 'text-3xl',
md: 'text-2xl',
sm: 'text-base',
Expand Down
133 changes: 133 additions & 0 deletions apps/mobile/components/common/tab-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { cn } from '@/lib/utils'
import type { BottomTabBarProps } from '@react-navigation/bottom-tabs'
import * as Haptics from 'expo-haptics'
import { Link } from 'expo-router'
import {
CogIcon,
LandPlotIcon,
type LucideIcon,
PlusIcon,
WalletIcon,
} from 'lucide-react-native'
import { rem } from 'nativewind'
import { Pressable, type PressableProps, View } from 'react-native'
import Animated, {
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated'
import { Button } from '../ui/button'
import { Separator } from '../ui/separator'

type TabBarItemProps = {
focused: boolean
icon: LucideIcon
descriptor: BottomTabBarProps['descriptors'][string]
}
function TabBarItem({
icon: Icon,
focused,
descriptor,
...props
}: TabBarItemProps & PressableProps) {
const { options } = descriptor
return (
<Pressable
accessibilityRole="button"
accessibilityState={focused ? { selected: true } : {}}
accessibilityLabel={options.tabBarAccessibilityLabel}
className={'h-12 w-12 items-center justify-center'}
{...props}
>
<Icon
className={cn(
'size-6',
focused ? 'text-primary-foreground' : 'text-muted-foreground',
)}
/>
</Pressable>
)
}

function NewRecordButton() {
return (
<Link
href="/transaction/new-record"
asChild
onPress={Haptics.selectionAsync}
>
<Button
size="icon"
className={cn(
'h-12 w-12 items-center justify-center rounded-xl border border-border bg-muted active:bg-muted/75',
)}
>
<PlusIcon className="size-6 text-foreground" />
</Button>
</Link>
)
}

const TAB_BAR_ICONS = {
index: WalletIcon,
budgets: LandPlotIcon,
settings: CogIcon,
}

const TAB_BAR_ITEM_WIDTH = (3 + 0.75) * rem.get()

export function TabBar({ state, descriptors, navigation }: BottomTabBarProps) {
const tabIndicatorPosition = useSharedValue(0)

const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: tabIndicatorPosition.value }],
}))

return (
<View className="absolute bottom-9 flex-row items-center justify-center gap-3 self-center rounded-2xl border border-border bg-background p-2">
<Animated.View
style={[animatedStyle]}
className="absolute left-2 h-12 w-12 rounded-xl bg-primary"
/>
{state.routes.map((route, index) => {
function onPress() {
Haptics.selectionAsync()

tabIndicatorPosition.value = withTiming(index * TAB_BAR_ITEM_WIDTH, {
duration: 300,
})

const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
})

if (state.index !== index && !event.defaultPrevented) {
navigation.navigate(route.name, route.params)
}
}

function onLongPress() {
navigation.emit({
type: 'tabLongPress',
target: route.key,
})
}

return (
<TabBarItem
key={route.key}
icon={TAB_BAR_ICONS[route.name as keyof typeof TAB_BAR_ICONS]}
focused={state.index === index}
descriptor={descriptors[route.key]}
onPress={onPress}
onLongPress={onLongPress}
/>
)
})}
<Separator orientation="vertical" className="h-8" />
<NewRecordButton />
</View>
)
}
2 changes: 1 addition & 1 deletion apps/mobile/components/home/wallet-statistics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export function WalletStatistics({
className="!border-0 h-auto native:h-auto flex-col items-center gap-3"
>
<View className="self-center">
<Text className="w-fit self-center text-center leading-tight">
<Text className="w-fit self-center text-center text-muted-foreground leading-tight">
{options.find((option) => option.value === view)?.label}
</Text>
</View>
Expand Down
13 changes: 1 addition & 12 deletions apps/mobile/components/primitives/tabs/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,7 @@ interface RootContext extends TabsRootProps {
const TabsContext = React.createContext<RootContext | null>(null)

const Root = React.forwardRef<ViewRef, SlottableViewProps & TabsRootProps>(
(
{
asChild,
value,
onValueChange,
orientation: Orientation,
dir: Dir,
activationMode: ActivationMode,
...viewProps
},
ref,
) => {
({ asChild, value, onValueChange, ...viewProps }, ref) => {
const nativeID = React.useId()
const Component = asChild ? Slot.View : View
return (
Expand Down
Loading

0 comments on commit c14922a

Please sign in to comment.