From dfd348a67eaf1d02a0c3cfe958bd3be9e46e0346 Mon Sep 17 00:00:00 2001 From: skalthoff <32023561+skalthoff@users.noreply.github.com> Date: Fri, 5 Dec 2025 18:33:10 -0800 Subject: [PATCH] refactor: enhance settings tab components with lazy loading and improved structure (#783) --- src/components/Settings/component.tsx | 81 ++++++++-- .../Settings/components/info-tab.tsx | 82 +++++----- .../Settings/components/preferences-tab.tsx | 150 +++++++++--------- .../components/settings-list-group.tsx | 61 ++++--- 4 files changed, 223 insertions(+), 151 deletions(-) diff --git a/src/components/Settings/component.tsx b/src/components/Settings/component.tsx index 23754864..3770ab37 100644 --- a/src/components/Settings/component.tsx +++ b/src/components/Settings/component.tsx @@ -1,16 +1,66 @@ -import React from 'react' +import React, { Suspense, lazy } from 'react' import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs' -import { getToken, getTokenValue, useTheme } from 'tamagui' -import AccountTab from './components/account-tab' -import Icon from '../Global/components/icon' -import PreferencesTab from './components/preferences-tab' -import PlaybackTab from './components/playback-tab' -import InfoTab from './components/info-tab' +import { getToken, getTokenValue, useTheme, Spinner, YStack } from 'tamagui' import SettingsTabBar from './tab-bar' -import StorageTab from './components/usage-tab' -import { SafeAreaView } from 'react-native-safe-area-context' + +// Lazy load tab components to improve initial render +const PreferencesTab = lazy(() => import('./components/preferences-tab')) +const PlaybackTab = lazy(() => import('./components/playback-tab')) +const StorageTab = lazy(() => import('./components/usage-tab')) +const AccountTab = lazy(() => import('./components/account-tab')) +const InfoTab = lazy(() => import('./components/info-tab')) + const SettingsTabsNavigator = createMaterialTopTabNavigator() +function TabFallback() { + return ( + + + + ) +} + +// Wrap lazy components with Suspense +function LazyPreferencesTab() { + return ( + }> + + + ) +} + +function LazyPlaybackTab() { + return ( + }> + + + ) +} + +function LazyStorageTab() { + return ( + }> + + + ) +} + +function LazyAccountTab() { + return ( + }> + + + ) +} + +function LazyInfoTab() { + return ( + }> + + + ) +} + export default function Settings(): React.JSX.Element { const theme = useTheme() @@ -30,13 +80,14 @@ export default function Settings(): React.JSX.Element { fontFamily: 'Figtree-Bold', }, tabBarPressOpacity: 0.5, - lazy: true, // Enable lazy loading to prevent all tabs from mounting simultaneously + lazy: true, + lazyPreloadDistance: 0, // Only load the active tab }} tabBar={(props) => } > - + - + - + {/* + {patrons.map((patron, index) => ( + + + {patron.fullName} + + + ))} + + ) +} + export default function InfoTab() { const patrons = usePatrons() @@ -101,47 +117,37 @@ export default function InfoTab() { iconName: 'hand-heart', iconColor: '$secondary', children: ( - + + Linking.openURL( + 'https://github.com/sponsors/anultravioletaurora/', + ) + } > - - Linking.openURL( - 'https://github.com/sponsors/anultravioletaurora/', - ) - } - > - - Sponsors - - - Linking.openURL( - 'https://patreon.com/anultravioletaurora', - ) - } - > - - Patreon - + + Sponsors - } - numColumns={2} - renderItem={({ item }) => ( - - - {item.fullName} - + + Linking.openURL( + 'https://patreon.com/anultravioletaurora', + ) + } + > + + Patreon - )} - /> + + + ), }, ]} diff --git a/src/components/Settings/components/preferences-tab.tsx b/src/components/Settings/components/preferences-tab.tsx index 5a58a15b..6d4a5b16 100644 --- a/src/components/Settings/components/preferences-tab.tsx +++ b/src/components/Settings/components/preferences-tab.tsx @@ -9,7 +9,6 @@ import { useThemeSetting, } from '../../../stores/settings/app' import { useSwipeSettingsStore } from '../../../stores/settings/swipe' -import { useMemo } from 'react' import Button from '../../Global/helpers/button' import Icon from '../../Global/components/icon' @@ -42,31 +41,20 @@ const THEME_OPTIONS: ThemeOptionConfig[] = [ }, ] -export default function PreferencesTab(): React.JSX.Element { - const [sendMetrics, setSendMetrics] = useSendMetricsSetting() - const [reducedHaptics, setReducedHaptics] = useReducedHapticsSetting() - const [themeSetting, setThemeSetting] = useThemeSetting() - - const [hideRunTimes, setHideRunTimes] = useHideRunTimesSetting() - - const left = useSwipeSettingsStore((s) => s.left) - const right = useSwipeSettingsStore((s) => s.right) - const toggleLeft = useSwipeSettingsStore((s) => s.toggleLeft) - const toggleRight = useSwipeSettingsStore((s) => s.toggleRight) - - const ActionChip = ({ - active, - label, - icon, - onPress, - testID, - }: { - active: boolean - label: string - icon: string - onPress: () => void - testID?: string - }) => ( +function ActionChip({ + active, + label, + icon, + onPress, + testID, +}: { + active: boolean + label: string + icon: string + onPress: () => void + testID?: string +}) { + return ( ) +} - const themeSubtitle = useMemo(() => { - switch (themeSetting) { - case 'light': - return 'You crazy diamond' - case 'dark': - return "There's a dark side??" - case 'oled': - return 'Back in black' - default: - return "I'm down with this system" - } - }, [themeSetting]) +function ThemeOptionCard({ + option, + isSelected, + onPress, +}: { + option: ThemeOptionConfig + isSelected: boolean + onPress: () => void +}) { + return ( + + + + + {option.label} + + {isSelected && } + + + ) +} + +function getThemeSubtitle(themeSetting: ThemeSetting): string { + switch (themeSetting) { + case 'light': + return 'You crazy diamond' + case 'dark': + return "There's a dark side??" + case 'oled': + return 'Back in black' + default: + return "I'm down with this system" + } +} + +export default function PreferencesTab(): React.JSX.Element { + const [sendMetrics, setSendMetrics] = useSendMetricsSetting() + const [reducedHaptics, setReducedHaptics] = useReducedHapticsSetting() + const [themeSetting, setThemeSetting] = useThemeSetting() + const [hideRunTimes, setHideRunTimes] = useHideRunTimesSetting() + + const left = useSwipeSettingsStore((s) => s.left) + const right = useSwipeSettingsStore((s) => s.right) + const toggleLeft = useSwipeSettingsStore((s) => s.toggleLeft) + const toggleRight = useSwipeSettingsStore((s) => s.toggleRight) + + const themeSubtitle = getThemeSubtitle(themeSetting) return ( ) } - -function ThemeOptionCard({ - option, - isSelected, - onPress, -}: { - option: ThemeOptionConfig - isSelected: boolean - onPress: () => void -}) { - return ( - - - - - {option.label} - - {isSelected && } - - - ) -} diff --git a/src/components/Settings/components/settings-list-group.tsx b/src/components/Settings/components/settings-list-group.tsx index f0c1dab8..42568969 100644 --- a/src/components/Settings/components/settings-list-group.tsx +++ b/src/components/Settings/components/settings-list-group.tsx @@ -11,6 +11,38 @@ interface SettingsListGroupProps { borderColor?: ThemeTokens } +function SettingsListItem({ + setting, + isLast, +}: { + setting: SettingsTabList[number] + isLast: boolean +}) { + return ( + <> + + } + subTitle={ + setting.subTitle && {setting.subTitle} + } + onPress={setting.onPress} + iconAfter={ + setting.onPress ? ( + + ) : undefined + } + > + {setting.children} + + + {!isLast && } + + ) +} + export default function SettingsListGroup({ settingsList, borderColor, @@ -25,30 +57,11 @@ export default function SettingsListGroup({ margin={'$3'} > {settingsList.map((setting, index, self) => ( - <> - - } - subTitle={ - setting.subTitle && ( - {setting.subTitle} - ) - } - onPress={setting.onPress} - iconAfter={ - setting.onPress ? ( - - ) : undefined - } - > - {setting.children} - - - - {index !== self.length - 1 && } - + ))} {footer}