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}