Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 3 additions & 18 deletions components/pages/SettingsScreen.tsx
Original file line number Diff line number Diff line change
@@ -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');

Expand Down Expand Up @@ -96,13 +97,6 @@ interface SettingsSection {
rows: SettingsRow[];
}

interface PrefsState {
darkMode: boolean;
notifications: boolean;
biometric: boolean;
autoPay: boolean;
}

const SECTIONS: SettingsSection[] = [
{
title: 'ACCOUNT',
Expand Down Expand Up @@ -138,16 +132,7 @@ const SECTIONS: SettingsSection[] = [

const SettingsScreen: React.FC<SettingsScreenProps> = ({ onBack }) => {
const insets = useSafeAreaInsets();
const [prefs, setPrefs] = useState<PrefsState>({
darkMode: false,
notifications: true,
biometric: false,
autoPay: true,
});

const togglePref = (key: keyof PrefsState) => {
setPrefs((prev) => ({ ...prev, [key]: !prev[key] }));
};
const { prefs, togglePref } = useSettingsScreen();

return (
<View style={{ flex: 1, backgroundColor: colors.settingsBg }}>
Expand Down
95 changes: 95 additions & 0 deletions hooks/settings/use-settings-screen.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
69 changes: 69 additions & 0 deletions hooks/settings/use-settings-screen.ts
Original file line number Diff line number Diff line change
@@ -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<PrefsState>(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,
};
};