init
This commit is contained in:
548
src/components/settings.tsx
Normal file
548
src/components/settings.tsx
Normal file
@@ -0,0 +1,548 @@
|
||||
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}
|
||||
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',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user