549 lines
18 KiB
TypeScript
549 lines
18 KiB
TypeScript
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',
|
||
},
|
||
});
|