From 726f3ed3e77f45043b14509008f30c0fd3372d44 Mon Sep 17 00:00:00 2001 From: Arome8240 Date: Fri, 27 Mar 2026 15:17:59 +0100 Subject: [PATCH] Extract Settings screen logic into useSettingsScreen hook - Created hooks/settings/use-settings-screen.ts with typed interfaces - Moved preferences state and togglePref handler from SettingsScreen - Refactored SettingsScreen to be presentation-only component - Added comprehensive tests for hook logic - Preserved all toggle behavior, animations, and accessibility labels - Follows repository hook patterns (typed returns, useCallback, JSDoc) Resolves #36 --- components/pages/SettingsScreen.tsx | 21 +---- hooks/settings/use-settings-screen.test.ts | 95 ++++++++++++++++++++++ hooks/settings/use-settings-screen.ts | 69 ++++++++++++++++ 3 files changed, 167 insertions(+), 18 deletions(-) create mode 100644 hooks/settings/use-settings-screen.test.ts create mode 100644 hooks/settings/use-settings-screen.ts diff --git a/components/pages/SettingsScreen.tsx b/components/pages/SettingsScreen.tsx index de2966e..3f37255 100644 --- a/components/pages/SettingsScreen.tsx +++ b/components/pages/SettingsScreen.tsx @@ -1,7 +1,8 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useRef, useEffect } from 'react'; import { View, Text, TouchableOpacity, ScrollView, Animated } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Ionicons } from '@expo/vector-icons'; +import { useSettingsScreen, PrefsState } from '../../hooks/settings/use-settings-screen'; // Centralized color palette shared with Tailwind const colors = require('../../theme/colors.json'); @@ -96,13 +97,6 @@ interface SettingsSection { rows: SettingsRow[]; } -interface PrefsState { - darkMode: boolean; - notifications: boolean; - biometric: boolean; - autoPay: boolean; -} - const SECTIONS: SettingsSection[] = [ { title: 'ACCOUNT', @@ -138,16 +132,7 @@ const SECTIONS: SettingsSection[] = [ const SettingsScreen: React.FC = ({ onBack }) => { const insets = useSafeAreaInsets(); - const [prefs, setPrefs] = useState({ - darkMode: false, - notifications: true, - biometric: false, - autoPay: true, - }); - - const togglePref = (key: keyof PrefsState) => { - setPrefs((prev) => ({ ...prev, [key]: !prev[key] })); - }; + const { prefs, togglePref } = useSettingsScreen(); return ( diff --git a/hooks/settings/use-settings-screen.test.ts b/hooks/settings/use-settings-screen.test.ts new file mode 100644 index 0000000..118b5e1 --- /dev/null +++ b/hooks/settings/use-settings-screen.test.ts @@ -0,0 +1,95 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import { useSettingsScreen } from './use-settings-screen'; + +declare var describe: any; +declare var it: any; +declare var expect: any; + +describe('useSettingsScreen', () => { + it('initializes with default preferences', () => { + const { result } = renderHook(() => useSettingsScreen()); + + expect(result.current.prefs).toEqual({ + darkMode: false, + notifications: true, + biometric: false, + autoPay: true, + }); + }); + + it('toggles darkMode preference', () => { + const { result } = renderHook(() => useSettingsScreen()); + + act(() => { + result.current.togglePref('darkMode'); + }); + + expect(result.current.prefs.darkMode).toBe(true); + + act(() => { + result.current.togglePref('darkMode'); + }); + + expect(result.current.prefs.darkMode).toBe(false); + }); + + it('toggles notifications preference', () => { + const { result } = renderHook(() => useSettingsScreen()); + + expect(result.current.prefs.notifications).toBe(true); + + act(() => { + result.current.togglePref('notifications'); + }); + + expect(result.current.prefs.notifications).toBe(false); + }); + + it('toggles biometric preference', () => { + const { result } = renderHook(() => useSettingsScreen()); + + expect(result.current.prefs.biometric).toBe(false); + + act(() => { + result.current.togglePref('biometric'); + }); + + expect(result.current.prefs.biometric).toBe(true); + }); + + it('toggles autoPay preference', () => { + const { result } = renderHook(() => useSettingsScreen()); + + expect(result.current.prefs.autoPay).toBe(true); + + act(() => { + result.current.togglePref('autoPay'); + }); + + expect(result.current.prefs.autoPay).toBe(false); + }); + + it('toggles multiple preferences independently', () => { + const { result } = renderHook(() => useSettingsScreen()); + + act(() => { + result.current.togglePref('darkMode'); + result.current.togglePref('biometric'); + }); + + expect(result.current.prefs.darkMode).toBe(true); + expect(result.current.prefs.biometric).toBe(true); + expect(result.current.prefs.notifications).toBe(true); // unchanged + expect(result.current.prefs.autoPay).toBe(true); // unchanged + }); + + it('creates toggle animation with correct initial value', () => { + const { result } = renderHook(() => useSettingsScreen()); + + const animationOn = result.current.createToggleAnimation(true); + const animationOff = result.current.createToggleAnimation(false); + + expect(animationOn.anim).toBeDefined(); + expect(animationOff.anim).toBeDefined(); + }); +}); diff --git a/hooks/settings/use-settings-screen.ts b/hooks/settings/use-settings-screen.ts new file mode 100644 index 0000000..cc7b208 --- /dev/null +++ b/hooks/settings/use-settings-screen.ts @@ -0,0 +1,69 @@ +import { useState, useCallback, useRef } from 'react'; +import { Animated } from 'react-native'; + +/** + * Preferences state for settings toggles + */ +export interface PrefsState { + darkMode: boolean; + notifications: boolean; + biometric: boolean; + autoPay: boolean; +} + +/** + * Animation state for a single toggle + */ +export interface ToggleAnimationState { + anim: Animated.Value; +} + +/** + * Return type for the useSettingsScreen hook + */ +export interface UseSettingsScreenReturn { + // Preferences state + prefs: PrefsState; + + // Handlers + togglePref: (key: keyof PrefsState) => void; + + // Animation factory for CustomToggle components + createToggleAnimation: (initialValue: boolean) => ToggleAnimationState; +} + +/** + * Initial preferences state + */ +const initialPrefsState: PrefsState = { + darkMode: false, + notifications: true, + biometric: false, + autoPay: true, +}; + +/** + * Custom hook for Settings Screen logic + * Handles preferences state, toggle handlers, and animation setup + */ +export const useSettingsScreen = (): UseSettingsScreenReturn => { + const [prefs, setPrefs] = useState(initialPrefsState); + + // Toggle preference handler + const togglePref = useCallback((key: keyof PrefsState) => { + setPrefs((prev: PrefsState) => ({ ...prev, [key]: !prev[key] })); + }, []); + + // Factory function to create animation state for toggles + const createToggleAnimation = useCallback((initialValue: boolean): ToggleAnimationState => { + return { + anim: new Animated.Value(initialValue ? 1 : 0), + }; + }, []); + + return { + prefs, + togglePref, + createToggleAnimation, + }; +};