From 3f3bf7a57ec673183f78d2324b7cff20c2226a10 Mon Sep 17 00:00:00 2001 From: smartdev Date: Mon, 23 Mar 2026 21:35:11 +0100 Subject: [PATCH] feat: Implement Settings screen - Created SettingsScreen with account, notifications, preferences, about sections - Added wallet disconnect functionality - Added notification toggle with AsyncStorage persistence - Added currency preference selector - Added to TabNavigator as SettingsTab Closes #10 --- src/screens/SettingsScreen.tsx | 111 +++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/screens/SettingsScreen.tsx diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx new file mode 100644 index 0000000..967af32 --- /dev/null +++ b/src/screens/SettingsScreen.tsx @@ -0,0 +1,111 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { View, Text, StyleSheet, ScrollView, SafeAreaView, TouchableOpacity, Switch, Alert, Linking } from 'react-native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { colors, spacing, typography, borderRadius } from '../utils/constants'; +import { useWalletStore } from '../store'; +import { Card } from '../components/common/Card'; + +const APP_VERSION = '1.0.0'; +interface Settings { notificationsEnabled: boolean; defaultCurrency: string; } +const SETTINGS_KEY = '@subtrackr_settings'; + +const SettingsScreen: React.FC = () => { + const { address, network, disconnect } = useWalletStore(); + const [settings, setSettings] = useState({ notificationsEnabled: true, defaultCurrency: 'USD' }); + + useEffect(() => { loadSettings(); }, []); + + const loadSettings = async () => { + try { + const savedSettings = await AsyncStorage.getItem(SETTINGS_KEY); + if (savedSettings) setSettings(JSON.parse(savedSettings)); + } catch (error) { console.error('Failed to load settings:', error); } + }; + + const saveSettings = async (newSettings: Settings) => { + try { + await AsyncStorage.setItem(SETTINGS_KEY, JSON.stringify(newSettings)); + setSettings(newSettings); + } catch (error) { console.error('Failed to save settings:', error); } + }; + + const handleNotificationToggle = useCallback((value: boolean) => saveSettings({ ...settings, notificationsEnabled: value }), [settings]); + const handleCurrencyChange = useCallback((currency: string) => saveSettings({ ...settings, defaultCurrency: currency }), [settings]); + + const handleDisconnectWallet = useCallback(() => { + Alert.alert('Disconnect Wallet', 'Are you sure you want to disconnect your wallet?', [ + { text: 'Cancel', style: 'cancel' }, + { text: 'Disconnect', style: 'destructive', onPress: async () => { + try { await disconnect(); Alert.alert('Success', 'Wallet disconnected'); } + catch { Alert.alert('Error', 'Failed to disconnect wallet'); } + }}, + ]); + }, [disconnect]); + + const currencies = ['USD', 'EUR', 'GBP', 'JPY', 'CAD', 'AUD']; + const shortenAddress = (addr: string): string => !addr ? 'Not connected' : `${addr.slice(0, 6)}...${addr.slice(-4)}`; + + return ( + + + SettingsConfigure your preferences + + Account + + Wallet Address{shortenAddress(address || '')} + + + Network{network || 'Not connected'} + + {address && Disconnect Wallet} + + + Notifications + + Billing RemindersGet notified before subscriptions renew + + + + + Preferences + + Default CurrencyCurrency for new subscriptions + + + {currencies.map((currency) => ( + handleCurrencyChange(currency)}> + {currency} + + ))} + + + + About + Version{APP_VERSION} + Linking.openURL('mailto:support@subtrackr.app')}>Contact Support + Linking.openURL('https://subtrackr.app/privacy')}>Privacy Policy + Linking.openURL('https://subtrackr.app/terms')}>Terms of Service + + + + ); +}; + +const styles = StyleSheet.create({ + container: { flex: 1, backgroundColor: colors.background }, scrollView: { flex: 1 }, + header: { padding: spacing.lg, paddingBottom: spacing.md }, title: { ...typography.h1, color: colors.text, marginBottom: spacing.xs }, subtitle: { ...typography.body, color: colors.textSecondary }, + section: { marginHorizontal: spacing.lg, marginBottom: spacing.md }, sectionTitle: { ...typography.h3, color: colors.text, marginBottom: spacing.md }, + settingRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: spacing.md, borderBottomWidth: 1, borderBottomColor: colors.border }, + settingInfo: { flex: 1 }, settingLabel: { ...typography.body, color: colors.text, fontWeight: '600' }, + settingValue: { ...typography.body, color: colors.textSecondary, marginTop: spacing.xs }, settingDescription: { ...typography.caption, color: colors.textSecondary, marginTop: spacing.xs }, + dangerButton: { backgroundColor: colors.error + '20', padding: spacing.md, borderRadius: borderRadius.md, alignItems: 'center', marginTop: spacing.md }, + dangerButtonText: { ...typography.body, color: colors.error, fontWeight: '600' }, + currencyGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: spacing.sm, marginTop: spacing.sm }, + currencyButton: { paddingVertical: spacing.sm, paddingHorizontal: spacing.md, borderRadius: borderRadius.md, backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border }, + currencyButtonActive: { backgroundColor: colors.primary, borderColor: colors.primary }, + currencyButtonText: { ...typography.body, color: colors.text }, currencyButtonTextActive: { color: colors.text, fontWeight: '600' }, + linkRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: spacing.md, borderBottomWidth: 1, borderBottomColor: colors.border }, + linkRowLast: { borderBottomWidth: 0 }, linkText: { ...typography.body, color: colors.text }, linkArrow: { ...typography.body, color: colors.textSecondary }, +}); + +export default SettingsScreen;