guardia-messenger/app/(tabs)/theme_settings.tsx

107 lines
5.1 KiB
TypeScript

import React, { useState, useEffect } from 'react'
import { View, Text, Switch, TouchableOpacity, StyleSheet, ScrollView, Alert } from 'react-native'
import * as SecureStore from 'expo-secure-store'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Haptics = (() => { try { return require('expo-haptics') } catch { return null } })()
import { COLORS } from '../../constants/Config'
import { useTheme } from '../../contexts/ThemeContext'
import { useFontScale } from '../../contexts/FontContext'
export default function ThemeSettingsScreen() {
const { isDark, toggleTheme } = useTheme()
const { fontScale, setFontScale } = useFontScale()
const [vibration, setVibration] = useState('short')
const [colorBlind, setColorBlind] = useState('default')
const [screenLock, setScreenLock] = useState(false)
useEffect(() => {
Promise.all([
SecureStore.getItemAsync('grd_vibration'),
SecureStore.getItemAsync('grd_colorblind'),
SecureStore.getItemAsync('grd_screen_lock'),
]).then(([v, c, l]) => {
if (v) setVibration(v)
if (c) setColorBlind(c)
if (l) setScreenLock(l === 'true')
})
}, [])
const saveVibration = async (v: string) => {
setVibration(v)
await SecureStore.setItemAsync('grd_vibration', v)
if (v !== 'none') await Haptics?.notificationAsync?.(Haptics?.NotificationFeedbackType?.Success)
}
const saveColorBlind = async (c: string) => {
setColorBlind(c)
await SecureStore.setItemAsync('grd_colorblind', c)
Alert.alert('색맹 지원', `${c === 'default' ? '기본' : c === 'protanopia' ? '제1색맹' : '제2색맹'} 팔레트가 적용됐습니다.`)
}
const toggleScreenLock = async (v: boolean) => {
setScreenLock(v)
await SecureStore.setItemAsync('grd_screen_lock', String(v))
Alert.alert('화면 방향', v ? '세로 방향으로 고정됩니다.' : '자동 회전이 활성화됩니다.')
}
return (
<ScrollView style={s.container} contentContainerStyle={{ paddingBottom: 30 }}>
{/* 다크모드 */}
<Text style={s.section}></Text>
<View style={s.row}>
<Text style={s.label}></Text>
<Switch value={isDark} onValueChange={toggleTheme} trackColor={{ true: COLORS.accent }} />
</View>
{/* 글자 크기 */}
<Text style={s.section}> </Text>
{([1.0, 1.2, 1.5] as const).map(scale => (
<TouchableOpacity key={scale} style={[s.option, fontScale === scale && s.optionActive]} onPress={() => setFontScale(scale)}>
<Text style={[s.optionText, fontScale === scale && s.optionTextActive, { fontSize: 14 * scale }]}>
{scale === 1.0 ? '작게 (기본)' : scale === 1.2 ? '보통' : '크게'}
</Text>
{fontScale === scale && <Text style={s.check}></Text>}
</TouchableOpacity>
))}
{/* 진동 패턴 */}
<Text style={s.section}> </Text>
{[['none', '없음'], ['short', '짧게'], ['long', '길게']].map(([v, label]) => (
<TouchableOpacity key={v} style={[s.option, vibration === v && s.optionActive]} onPress={() => saveVibration(v)}>
<Text style={[s.optionText, vibration === v && s.optionTextActive]}>{label}</Text>
{vibration === v && <Text style={s.check}></Text>}
</TouchableOpacity>
))}
{/* 색맹 지원 */}
<Text style={s.section}> </Text>
{[['default', '기본'], ['protanopia', '제1색맹 (적록)'], ['deuteranopia', '제2색맹 (녹적)']].map(([c, label]) => (
<TouchableOpacity key={c} style={[s.option, colorBlind === c && s.optionActive]} onPress={() => saveColorBlind(c)}>
<Text style={[s.optionText, colorBlind === c && s.optionTextActive]}>{label}</Text>
{colorBlind === c && <Text style={s.check}></Text>}
</TouchableOpacity>
))}
{/* 화면 방향 잠금 */}
<Text style={s.section}> </Text>
<View style={s.row}>
<Text style={s.label}> </Text>
<Switch value={screenLock} onValueChange={toggleScreenLock} trackColor={{ true: COLORS.accent }} />
</View>
</ScrollView>
)
}
const s = StyleSheet.create({
container: { flex: 1, backgroundColor: COLORS.bg },
section: { fontSize: 13, fontWeight: '700', color: COLORS.muted, paddingHorizontal: 16, paddingTop: 20, paddingBottom: 6, textTransform: 'uppercase', letterSpacing: 1 },
row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', backgroundColor: '#fff', paddingHorizontal: 16, paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: COLORS.border },
label: { fontSize: 15, color: COLORS.text },
option: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', backgroundColor: '#fff', paddingHorizontal: 16, paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: COLORS.border },
optionActive: { backgroundColor: COLORS.light },
optionText: { fontSize: 15, color: COLORS.text },
optionTextActive:{ color: COLORS.accent, fontWeight: '600' },
check: { fontSize: 16, color: COLORS.accent },
})