Skip to content

Commit 95f2aac

Browse files
committed
Add skeleton animation
1 parent 0ed983b commit 95f2aac

File tree

12 files changed

+149
-36
lines changed

12 files changed

+149
-36
lines changed

src/components/DoubleSkeleton.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React, { useMemo } from 'react'
2+
import { View } from 'react-native'
3+
import { RoundedIconSkeleton } from './RoundedIcon'
4+
import { Skeleton } from './Skeleton'
5+
6+
export default function DoubleSkeleton({ n }: { n: number }) {
7+
const arr = useMemo(() => Array.from({ length: n }), [n])
8+
return (
9+
<>
10+
{arr.map((_, i) => (
11+
<UserSkeleton key={i} />
12+
))}
13+
</>
14+
)
15+
}
16+
17+
function UserSkeleton() {
18+
const randomWidth1 = useMemo(() => Math.random() * 50 + 30, []) // Random width between 30% and 80%
19+
const randomWidth2 = useMemo(() => Math.random() * 50 + 30, []) // Random width between 30% and 80%
20+
21+
return (
22+
<Skeleton>
23+
<View className='my-2.5 flex-row items-center px-6'>
24+
<RoundedIconSkeleton />
25+
<View className='ml-4 flex-1'>
26+
<View style={{ width: `${randomWidth1}%` }} className='h-4 rounded-md bg-zinc-200 dark:bg-zinc-800' />
27+
<View
28+
style={{ width: `${randomWidth2}%` }}
29+
className='mt-1.5 h-3.5 rounded-md bg-zinc-200 dark:bg-zinc-800'
30+
/>
31+
</View>
32+
</View>
33+
</Skeleton>
34+
)
35+
}

src/components/RoundedIcon.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,11 @@ export default function RoundedIcon({ Icon, style, iconProps, ...props }: Rounde
2525
</View>
2626
)
2727
}
28+
29+
export function RoundedIconSkeleton() {
30+
return (
31+
<View style={[{ borderRadius: 10, padding: 7 }]} className='bg-zinc-200 dark:bg-zinc-800'>
32+
<View style={{ height: 18, width: 18 }} />
33+
</View>
34+
)
35+
}

src/components/SingleSkeleton.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React, { useMemo } from 'react'
2+
import { View } from 'react-native'
3+
import { RoundedIconSkeleton } from './RoundedIcon'
4+
import { Skeleton } from './Skeleton'
5+
6+
export default function SingleSkeleton({ n }: { n: number }) {
7+
const arr = useMemo(() => Array.from({ length: n }), [n])
8+
return (
9+
<>
10+
{arr.map((_, i) => (
11+
<UserSkeleton key={i} />
12+
))}
13+
</>
14+
)
15+
}
16+
17+
function UserSkeleton() {
18+
const randomWidth1 = useMemo(() => Math.random() * 50 + 30, []) // Random width between 30% and 80%
19+
20+
return (
21+
<Skeleton>
22+
<View className='flex-row items-center px-6' style={{ marginVertical: 3 }}>
23+
<RoundedIconSkeleton />
24+
<View className='ml-4 flex-1'>
25+
<View style={{ width: `${randomWidth1}%` }} className='h-4 rounded-md bg-zinc-200 dark:bg-zinc-800' />
26+
</View>
27+
</View>
28+
</Skeleton>
29+
)
30+
}

src/components/Skeleton.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React, { useEffect } from 'react'
2+
import type { ViewProps } from 'react-native'
3+
import { useSharedValue, withRepeat, withTiming, useAnimatedStyle } from 'react-native-reanimated'
4+
import Animated, { Easing } from 'react-native-reanimated'
5+
6+
export function Skeleton({ children }: { children: React.ReactNode }) {
7+
const opacity = useSharedValue(1)
8+
9+
useEffect(() => {
10+
const randomDuration = Math.random() * 700 + 500
11+
opacity.value = withRepeat(
12+
withTiming(0.5, {
13+
duration: randomDuration,
14+
easing: Easing.inOut(Easing.ease),
15+
}),
16+
-1,
17+
true,
18+
)
19+
// eslint-disable-next-line react-hooks/exhaustive-deps
20+
}, [])
21+
22+
const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.value }))
23+
return <Animated.View style={animatedStyle}>{children}</Animated.View>
24+
}

src/screens/DeveloperOptions/MMKVDataList.tsx

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import { Gap12 } from '@components/Gap'
55
import RoundedIcon from '@components/RoundedIcon'
66
import Search from '@components/Search'
77
import { SettGroup, SettOption, SettText, SettWrapper } from '@components/Settings'
8+
import SingleSkeleton from '@components/SingleSkeleton'
89
import { useIsFocused } from '@react-navigation/native'
910
import { Colors } from '@utils/colors'
1011
import { ls, secureLs, type StorageKeys } from '@utils/storage'
1112
import type { NavProp } from '@utils/types'
12-
import { screenDelay } from '@utils/utils'
13+
import { delayedFadeAnimationSearch, screenDelay } from '@utils/utils'
1314
import React, { useEffect } from 'react'
14-
import { ActivityIndicator, View } from 'react-native'
15-
import Animated, { FadeIn } from 'react-native-reanimated'
15+
import { View } from 'react-native'
16+
import Animated from 'react-native-reanimated'
1617

1718
export default function MMKVDataList({ navigation }: NavProp) {
1819
const state = useIsFocused()
@@ -21,7 +22,7 @@ export default function MMKVDataList({ navigation }: NavProp) {
2122
const [searchResults, setSearchResults] = React.useState<string[]>([])
2223

2324
useEffect(() => {
24-
if (state) screenDelay(() => setInitStorage([...ls.getAllKeys(), ...secureLs.getAllKeys()]))
25+
if (state) screenDelay(() => setInitStorage([...ls.getAllKeys(), ...secureLs.getAllKeys()]), 700)
2526
// setInitStorage([])
2627
}, [state])
2728

@@ -61,7 +62,7 @@ export default function MMKVDataList({ navigation }: NavProp) {
6162

6263
<SettGroup title='Stored keys' className='pb-4'>
6364
{searchResults?.map((item, i) => (
64-
<Animated.View key={item} entering={delayedFadeAnimation(search, i)}>
65+
<Animated.View key={item} entering={delayedFadeAnimationSearch(search, i)}>
6566
<SettOption
6667
title={item}
6768
arrow
@@ -81,9 +82,7 @@ export default function MMKVDataList({ navigation }: NavProp) {
8182
/>
8283
</>
8384
)}
84-
{initStorage === null && (
85-
<ActivityIndicator size='large' color={Colors.accent} style={{ marginTop: 30, marginBottom: 30 }} />
86-
)}
85+
{initStorage === null && <SingleSkeleton n={18} />}
8786
{searchResults.length === 0 && (
8887
<SettText className='mt-2 pl-6'>No data found. Try searching with another keyword.</SettText>
8988
)}
@@ -104,7 +103,3 @@ export default function MMKVDataList({ navigation }: NavProp) {
104103
</>
105104
)
106105
}
107-
108-
function delayedFadeAnimation(search: string, i: number) {
109-
return FadeIn.duration(250).delay(search.trim().length === 0 ? Math.min((i + 1) * 25, 500) : 20)
110-
}

src/screens/Settings/AppLock/AllUsers.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import { UserSolidIcon } from '@assets/icons/icons'
22
import BackHeader from '@components/BackHeader'
3+
import DoubleSkeleton from '@components/DoubleSkeleton'
34
import { Gap12 } from '@components/Gap'
45
import RoundedIcon from '@components/RoundedIcon'
56
import Search from '@components/Search'
67
import { SettGroup, SettOption, SettText, SettWrapper } from '@components/Settings'
78
import { useQuery } from '@tanstack/react-query'
89
import { client } from '@utils/client'
9-
import { Colors } from '@utils/colors'
1010
import { F, Medium } from '@utils/fonts'
1111
import type { NavProp } from '@utils/types'
12+
import { delayedFadeAnimation } from '@utils/utils'
1213
import React, { useEffect } from 'react'
13-
import { ActivityIndicator, ToastAndroid, View } from 'react-native'
14+
import { ToastAndroid, View } from 'react-native'
15+
import Animated from 'react-native-reanimated'
1416

1517
export default function AllUsers({ navigation }: NavProp) {
1618
const [search, setSearch] = React.useState('')
@@ -46,13 +48,15 @@ export default function AllUsers({ navigation }: NavProp) {
4648
<Gap12>
4749
<SettText className='mt-3'>You can see all users here. Also you can add or remove users.</SettText>
4850
<SettGroup title='All users'>
49-
{isPending && <ActivityIndicator color={Colors.accent} size='large' className='mb-10 mt-5' />}
50-
{data?.data?.map((user) => (
51-
<SettOption title={user.name} key={user.email} Icon={<RoundedIcon Icon={UserSolidIcon} />} arrow>
52-
<Medium className='text-zinc-600 dark:text-zinc-400' style={F.F10_5} numberOfLines={1}>
53-
{user.email}
54-
</Medium>
55-
</SettOption>
51+
{isPending && <DoubleSkeleton n={12} />}
52+
{data?.data?.map((user, i) => (
53+
<Animated.View key={user.email} entering={delayedFadeAnimation(i)}>
54+
<SettOption title={user.name} Icon={<RoundedIcon Icon={UserSolidIcon} />} arrow>
55+
<Medium className='text-zinc-600 dark:text-zinc-400' style={F.F10_5} numberOfLines={1}>
56+
{user.email}
57+
</Medium>
58+
</SettOption>
59+
</Animated.View>
5660
))}
5761
</SettGroup>
5862
</Gap12>

src/screens/Settings/Devices/Devices.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import { WavingHand02SolidIcon } from '@assets/icons/icons'
2+
import DoubleSkeleton from '@components/DoubleSkeleton'
23
import { Gap12 } from '@components/Gap'
34
import RoundedIcon from '@components/RoundedIcon'
45
import { SettGroup, SettOption, SettText, SettWrapper } from '@components/Settings'
56
import { useMutation, useQuery } from '@tanstack/react-query'
67
import { client } from '@utils/client'
7-
import { Colors } from '@utils/colors'
88
import { F, Medium } from '@utils/fonts'
99
import { getRelativeTime } from '@utils/timeFormat'
1010
import type { NavProp, StackNav } from '@utils/types'
11+
import { delayedFadeAnimation } from '@utils/utils'
1112
import React, { useEffect, useMemo } from 'react'
12-
import { ActivityIndicator, Alert, ToastAndroid } from 'react-native'
13+
import { Alert, ToastAndroid } from 'react-native'
14+
import Animated from 'react-native-reanimated'
1315
import type { Device } from './types'
1416
import { getDeviceIcon } from './utils'
1517

@@ -46,7 +48,7 @@ export default function Devices({ navigation }: NavProp) {
4648
<Gap12>
4749
<SettText className='mt-3'>You can remove unwanted devices from the list.</SettText>
4850
<SettGroup title='This Device'>
49-
{isPending && <ActivityIndicator size='large' color={Colors.accent} className='mb-10 mt-5' />}
51+
{isPending && <DoubleSkeleton n={1} />}
5052
{data && data.data && <Device navigation={navigation} device={data.data.currentDevice} isSelf={true} />}
5153
</SettGroup>
5254
{data && data.data && data.data.devices.length > 0 && (
@@ -61,13 +63,18 @@ export default function Devices({ navigation }: NavProp) {
6163
</SettGroup>
6264
)}
6365

64-
{data && data.data && data.data.devices.length > 0 && (
65-
<SettGroup title='Other Devices'>
66-
{data.data.devices.map((device, i) => (
67-
<Device key={i} navigation={navigation} device={device} isSelf />
68-
))}
69-
</SettGroup>
70-
)}
66+
<SettGroup title='Other Devices'>
67+
{isPending && <DoubleSkeleton n={10} />}
68+
{data && data.data && data.data.devices.length > 0 && (
69+
<>
70+
{data.data.devices.map((device, i) => (
71+
<Animated.View key={device?.id} entering={delayedFadeAnimation(i)}>
72+
<Device key={i} navigation={navigation} device={device} isSelf />
73+
</Animated.View>
74+
))}
75+
</>
76+
)}
77+
</SettGroup>
7178
<SettText className='mt-2'>
7279
Click on a device to view more details. You can remove a device by clicking on the remove button.
7380
</SettText>

src/screens/Weather/Widget/WeatherWidget.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import React, { useCallback, useEffect } from 'react'
1010
import { ActivityIndicator, TouchableOpacity, View } from 'react-native'
1111
import { useDerivedValue, useSharedValue, withTiming } from 'react-native-reanimated'
1212
import { getWeather } from '../api'
13-
import type { Weather } from '../types'
1413
import { Icons } from '../utils'
1514

1615
const WeatherWidget = React.memo<{ navigation: StackNav }>(({ navigation }) => {

src/utils/client.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import hcWithType from '../rpc/hcWithType'
77
import { hc } from 'hono/dist/client/client'
88
import { WEB } from './constants'
99

10-
// const address = WEB
11-
const address = __DEV__ ? 'http://192.168.111.94:3000/' : WEB
10+
const address = WEB
11+
// const address = __DEV__ ? 'http://192.168.250.152:3000/' : WEB
12+
13+
console.log(process.env.LOCAL)
1214

1315
let client = (hc as typeof hcWithType)(address, {
1416
headers() {

src/utils/storage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ type CoordinateNotesStorage = Store<typeof CoordinateNotesStorage>
146146
type UserProfileStorage = Store<typeof UserProfileStorage>
147147
type AppLockStorage = Store<typeof AppLockStorage>
148148
type UserStorage = Store<typeof UserStorage>
149-
type misc = 'token'
149+
type misc = 'misc'
150150
export type StorageKeys =
151151
| WeatherStorage
152152
| DeveloperStorage

0 commit comments

Comments
 (0)