Files
RSSuper/src/components/settings.tsx
2026-03-29 09:21:33 -04:00

549 lines
18 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import { ScrollView, StyleSheet, Switch, View, TextInput, Pressable } from 'react-native';
import { useColorScheme } from 'react-native';
import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';
import { Collapsible } from '@/components/ui/collapsible';
import { SYNC_INTERVALS, SyncInterval, NotificationPreferences, ReadingPreferences } from '@/types/feed';
import { AccountSettings } from '@/types/global';
import { useTheme } from '@/hooks/use-theme';
import { useSettingsStore } from '@/stores/settings-store';
export default function SettingsScreen() {
const colorScheme = useColorScheme();
const theme = useTheme();
const {
syncInterval,
setSyncInterval,
theme: themeSetting,
setTheme,
notificationPreferences,
setNotificationPreferences,
readingPreferences,
setReadingPreferences,
accountSettings,
setAccountSettings,
} = useSettingsStore();
// Sync interval handler
const handleSyncIntervalChange = (value: number) => {
const interval = SYNC_INTERVALS.find((i) => i.value === value);
if (interval) {
setSyncInterval(interval);
}
};
// Notification handlers
const handleNotificationToggle = (key: keyof NotificationPreferences, value: boolean) => {
setNotificationPreferences({ [key]: value });
};
// Reading preference handlers
const handleReadingToggle = (key: keyof ReadingPreferences, value: boolean) => {
setReadingPreferences({ [key]: value });
};
const handleFontSizeChange = (size: ReadingPreferences['fontSize']) => {
setReadingPreferences({ fontSize: size });
};
const handleLineHeightChange = (height: ReadingPreferences['lineHeight']) => {
setReadingPreferences({ lineHeight: height });
};
// Theme handler
const handleThemeChange = (theme: 'light' | 'dark' | 'system') => {
setTheme(theme);
};
// Account settings handlers
const handleAccountSettingChange = (key: keyof AccountSettings, value: string | boolean) => {
setAccountSettings({ [key]: value });
};
return (
<ThemedView style={styles.container}>
<ScrollView style={styles.scrollView} contentContainerStyle={styles.scrollContent}>
{/* Sync Settings */}
<ThemedView style={styles.section}>
<ThemedText type="section" style={styles.sectionTitle}>Sync Settings</ThemedText>
<ThemedText type="defaultSemiBold" style={styles.sectionDescription}>
Choose how often your feeds should be updated.
</ThemedText>
<View style={styles.intervalContainer}>
{SYNC_INTERVALS.map((interval) => (
<Pressable
key={interval.value}
onPress={() => handleSyncIntervalChange(interval.value)}
style={({ pressed }) => [
styles.intervalItem,
{
backgroundColor:
syncInterval.value === interval.value
? theme.backgroundSelected
: theme.backgroundElement,
opacity: pressed ? 0.7 : 1,
},
]}
>
<ThemedText style={styles.intervalLabel}>
{interval.label}
</ThemedText>
{syncInterval.value === interval.value && (
<View style={styles.checkmark}>
<View style={styles.checkmarkCircle} />
</View>
)}
</Pressable>
))}
</View>
</ThemedView>
{/* Theme */}
<ThemedView style={styles.section}>
<ThemedText type="section" style={styles.sectionTitle}>Theme</ThemedText>
<ThemedText type="defaultSemiBold" style={styles.sectionDescription}>
Choose your preferred appearance.
</ThemedText>
<View style={styles.themeContainer}>
<Pressable
onPress={() => handleThemeChange('light')}
style={({ pressed }) => [
styles.themeButton,
{ opacity: pressed ? 0.7 : 1 },
]}
>
<View style={[
styles.themeCircle,
{ backgroundColor: themeSetting === 'light' ? '#ffffff' : theme.backgroundElement }
]} />
<ThemedText style={styles.themeLabel}>Light</ThemedText>
</Pressable>
<Pressable
onPress={() => handleThemeChange('dark')}
style={({ pressed }) => [
styles.themeButton,
{ opacity: pressed ? 0.7 : 1 },
]}
>
<View style={[
styles.themeCircle,
{ backgroundColor: themeSetting === 'dark' ? '#000000' : theme.backgroundElement }
]} />
<ThemedText style={styles.themeLabel}>Dark</ThemedText>
</Pressable>
<Pressable
onPress={() => handleThemeChange('system')}
style={({ pressed }) => [
styles.themeButton,
{ opacity: pressed ? 0.7 : 1 },
]}
>
<View style={[
styles.themeCircle,
{ backgroundColor: themeSetting === 'system' ? theme.backgroundSelected : theme.backgroundElement }
]} />
<ThemedText style={styles.themeLabel}>System</ThemedText>
</Pressable>
</View>
<ThemedView type="backgroundElement" style={styles.warningBox}>
<ThemedText type="smallBold" style={styles.warningTitle}> Note</ThemedText>
<ThemedText type="small" style={styles.warningText}>
Changing theme will apply on app restart.
</ThemedText>
</ThemedView>
</ThemedView>
{/* Notifications */}
<ThemedView style={styles.section}>
<ThemedText type="section" style={styles.sectionTitle}>Notifications</ThemedText>
<ThemedText type="defaultSemiBold" style={styles.sectionDescription}>
Manage how you receive notifications.
</ThemedText>
<ThemedView style={styles.prefGroup}>
<View style={styles.prefHeader}>
<ThemedText style={styles.prefTitle}>New Articles</ThemedText>
<Switch
value={notificationPreferences.newArticles}
onValueChange={(value) => handleNotificationToggle('newArticles', value)}
trackColor={{ false: theme.textSecondary, true: theme.text }}
thumbColor="white"
/>
</View>
</ThemedView>
<ThemedView style={styles.prefGroup}>
<View style={styles.prefHeader}>
<ThemedText style={styles.prefTitle}>Episode Releases</ThemedText>
<Switch
value={notificationPreferences.episodeReleases}
onValueChange={(value) => handleNotificationToggle('episodeReleases', value)}
trackColor={{ false: theme.textSecondary, true: theme.text }}
thumbColor="white"
/>
</View>
</ThemedView>
<ThemedView style={styles.prefGroup}>
<View style={styles.prefHeader}>
<ThemedText style={styles.prefTitle}>Custom Alerts</ThemedText>
<Switch
value={notificationPreferences.customAlerts}
onValueChange={(value) => handleNotificationToggle('customAlerts', value)}
trackColor={{ false: theme.textSecondary, true: theme.text }}
thumbColor="white"
/>
</View>
</ThemedView>
<Collapsible title="Sound & Vibration">
<ThemedView style={styles.prefGroup}>
<View style={styles.prefHeader}>
<ThemedText style={styles.prefTitle}>Sound</ThemedText>
<Switch
value={notificationPreferences.sound}
onValueChange={(value) => handleNotificationToggle('sound', value)}
trackColor={{ false: theme.textSecondary, true: theme.text }}
thumbColor="white"
/>
</View>
</ThemedView>
<ThemedView style={styles.prefGroup}>
<View style={styles.prefHeader}>
<ThemedText style={styles.prefTitle}>Vibration</ThemedText>
<Switch
value={notificationPreferences.vibration}
onValueChange={(value) => handleNotificationToggle('vibration', value)}
trackColor={{ false: theme.textSecondary, true: theme.text }}
thumbColor="white"
/>
</View>
</ThemedView>
</Collapsible>
</ThemedView>
{/* Reading Preferences */}
<ThemedView style={styles.section}>
<ThemedText type="section" style={styles.sectionTitle}>Reading Preferences</ThemedText>
<ThemedText type="defaultSemiBold" style={styles.sectionDescription}>
Customize your reading experience.
</ThemedText>
<ThemedView style={styles.prefGroup}>
<ThemedText style={styles.prefTitle}>Font Size</ThemedText>
<View style={styles.fontSizeSelector}>
{(['small', 'medium', 'large', 'xlarge'] as ReadingPreferences['fontSize'][]).map((size) => (
<Pressable
key={size}
onPress={() => handleFontSizeChange(size)}
style={({ pressed }) => [
styles.fontSizeButton,
{
backgroundColor:
readingPreferences.fontSize === size
? theme.backgroundSelected
: theme.backgroundElement,
opacity: pressed ? 0.7 : 1,
},
]}
>
<ThemedText style={[
styles.fontSizeLabel,
size === 'small' && { fontSize: 12 },
size === 'medium' && { fontSize: 16 },
size === 'large' && { fontSize: 20 },
size === 'xlarge' && { fontSize: 24 }
]}>
{size === 'small' ? 'A' : size === 'medium' ? 'B' : size === 'large' ? 'C' : 'D'}
</ThemedText>
</Pressable>
))}
</View>
</ThemedView>
<ThemedView style={styles.prefGroup}>
<ThemedText style={styles.prefTitle}>Line Height</ThemedText>
<View style={styles.lineHeightSelector}>
{(['normal', 'relaxed', 'loose'] as ReadingPreferences['lineHeight'][]).map((height) => (
<Pressable
key={height}
onPress={() => handleLineHeightChange(height)}
style={({ pressed }) => [
styles.lineHeightButton,
{
backgroundColor:
readingPreferences.lineHeight === height
? theme.backgroundSelected
: theme.backgroundElement,
opacity: pressed ? 0.7 : 1,
},
]}
>
<ThemedText style={styles.lineHeightLabel}>{height.charAt(0).toUpperCase() + height.slice(1)}</ThemedText>
</Pressable>
))}
</View>
</ThemedView>
<ThemedView style={styles.prefGroup}>
<View style={styles.prefHeader}>
<ThemedText style={styles.prefTitle}>Show Reading Time</ThemedText>
<Switch
value={readingPreferences.showReadingTime}
onValueChange={(value) => handleReadingToggle('showReadingTime', value)}
trackColor={{ false: theme.textSecondary, true: theme.text }}
thumbColor="white"
/>
</View>
</ThemedView>
<ThemedView style={styles.prefGroup}>
<View style={styles.prefHeader}>
<ThemedText style={styles.prefTitle}>Show Author</ThemedText>
<Switch
value={readingPreferences.showAuthor}
onValueChange={(value) => handleReadingToggle('showAuthor', value)}
trackColor={{ false: theme.textSecondary, true: theme.text }}
thumbColor="white"
/>
</View>
</ThemedView>
<ThemedView style={styles.prefGroup}>
<View style={styles.prefHeader}>
<ThemedText style={styles.prefTitle}>Show Date</ThemedText>
<Switch
value={readingPreferences.showDate}
onValueChange={(value) => handleReadingToggle('showDate', value)}
trackColor={{ false: theme.textSecondary, true: theme.text }}
thumbColor="white"
/>
</View>
</ThemedView>
</ThemedView>
{/* Account Settings */}
<ThemedView style={styles.section}>
<ThemedText type="section" style={styles.sectionTitle}>Account Settings</ThemedText>
<ThemedText type="defaultSemiBold" style={styles.sectionDescription}>
Manage your account preferences.
</ThemedText>
<ThemedView style={styles.prefGroup}>
<ThemedText style={styles.prefTitle}>Email</ThemedText>
<TextInput
style={[styles.input, { backgroundColor: theme.backgroundElement }]}
placeholder="Enter your email"
value={accountSettings.email}
onChangeText={(text) => handleAccountSettingChange('email', text)}
keyboardType="email-address"
autoCapitalize="none"
/>
</ThemedView>
<ThemedView style={styles.prefGroup}>
<ThemedText style={styles.prefTitle}>Privacy Level</ThemedText>
<View style={styles.privacySelector}>
{(['public', 'private', 'anonymous'] as AccountSettings['privacy'][]).map((privacy) => (
<Pressable
key={privacy}
onPress={() => handleAccountSettingChange('privacy', privacy)}
style={({ pressed }) => [
styles.privacyButton,
{
backgroundColor:
accountSettings.privacy === privacy
? theme.backgroundSelected
: theme.backgroundElement,
opacity: pressed ? 0.7 : 1,
},
]}
>
<ThemedText style={styles.privacyLabel}>
{privacy.charAt(0).toUpperCase() + privacy.slice(1)}
</ThemedText>
</Pressable>
))}
</View>
</ThemedView>
<ThemedView style={styles.prefGroup}>
<ThemedText style={styles.prefTitle}>Language</ThemedText>
<TextInput
style={[styles.input, { backgroundColor: theme.backgroundElement }]}
placeholder="e.g., en-US"
value={accountSettings.language}
onChangeText={(text) => handleAccountSettingChange('language', text)}
/>
</ThemedView>
<ThemedView style={styles.prefGroup}>
<ThemedText style={styles.prefTitle}>Timezone</ThemedText>
<TextInput
style={[styles.input, { backgroundColor: theme.backgroundElement }]}
placeholder="e.g., UTC"
value={accountSettings.timezone}
onChangeText={(text) => handleAccountSettingChange('timezone', text)}
/>
</ThemedView>
<ThemedView style={styles.prefGroup}>
<View style={styles.prefHeader}>
<ThemedText style={styles.prefTitle}>Notifications Enabled</ThemedText>
<Switch
value={accountSettings.notificationsEnabled}
onValueChange={(value) => handleAccountSettingChange('notificationsEnabled', value)}
trackColor={{ false: theme.textSecondary, true: theme.text }}
thumbColor="white"
/>
</View>
</ThemedView>
</ThemedView>
</ScrollView>
</ThemedView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
scrollView: {
flex: 1,
},
scrollContent: {
paddingBottom: 24,
},
section: {
marginBottom: 24,
paddingHorizontal: 16,
},
sectionTitle: {
marginBottom: 4,
},
sectionDescription: {
marginBottom: 16,
},
intervalContainer: {
gap: 8,
},
intervalItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 16,
borderRadius: 12,
gap: 12,
},
intervalLabel: {
fontSize: 14,
},
checkmark: {
width: 20,
height: 20,
borderRadius: 10,
justifyContent: 'center',
alignItems: 'center',
},
checkmarkCircle: {
width: 12,
height: 12,
borderRadius: 6,
backgroundColor: '#4CAF50',
},
themeContainer: {
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
marginBottom: 16,
},
themeButton: {
alignItems: 'center',
gap: 4,
},
themeCircle: {
width: 40,
height: 40,
borderRadius: 20,
},
themeLabel: {
fontSize: 12,
},
warningBox: {
padding: 12,
borderRadius: 8,
marginBottom: 16,
},
warningTitle: {
marginBottom: 4,
},
warningText: {},
prefGroup: {
marginBottom: 8,
},
prefHeader: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
prefTitle: {
fontSize: 14,
},
fontSizeSelector: {
flexDirection: 'row',
gap: 8,
},
fontSizeButton: {
padding: 12,
borderRadius: 8,
minWidth: 44,
alignItems: 'center',
justifyContent: 'center',
},
fontSizeLabel: {
fontWeight: 'bold',
},
lineHeightSelector: {
flexDirection: 'row',
gap: 8,
},
lineHeightButton: {
flex: 1,
padding: 12,
borderRadius: 8,
alignItems: 'center',
},
lineHeightLabel: {
fontSize: 12,
textAlign: 'center',
},
input: {
borderRadius: 8,
padding: 12,
fontSize: 14,
marginTop: 4,
},
privacySelector: {
flexDirection: 'row',
gap: 8,
marginTop: 4,
},
privacyButton: {
flex: 1,
padding: 12,
borderRadius: 8,
alignItems: 'center',
},
privacyLabel: {
fontSize: 14,
textAlign: 'center',
},
});