feat(mobile): Phase M-2 D3RO 디자인 시스템 모바일 적용

DS 컴포넌트 9개 (ScreenPanel, WaveBars, AppStatusBar, Header, FilterChip 신규),
5개 탭 + 로그인 화면 D3RO 인스트루먼트 미학 적용,
i18n 연결 (I18nProvider + 모바일 전용 키 80+개),
모노레포 workspaces에 apps/mobile 추가.
This commit is contained in:
yunchan8804 2026-04-13 17:32:31 +09:00
parent 541f04d02b
commit 55eb62189a
19 changed files with 10435 additions and 1073 deletions

View file

@ -1,22 +1,31 @@
// apps/mobile/app/(tabs)/settings.tsx
// 설정 탭 — 계정, 백엔드, 환경설정
// Phase M-2에서 디자인(docs/v3/designs/settings.html) 정밀 적용
// Settings tab — account, backend, preferences
// Design ref: docs/v3/designs/settings.html
import { View, ScrollView, StyleSheet, Alert, Switch } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { MetalCard, PhosphorText, PhysicalButton, Led, d3roNativePalette } from '@d3ro/ui-native'
import {
MetalCard,
PhosphorText,
PhysicalButton,
Led,
AppStatusBar,
d3roNativePalette
} from '@d3ro/ui-native'
import { useI18n } from '@d3ro/i18n'
import { useAuth } from '../../lib/auth-context'
import { supabase } from '../../lib/supabase'
export default function SettingsScreen(): React.ReactElement {
const insets = useSafeAreaInsets()
const { t } = useI18n()
const { user } = useAuth()
async function handleLogout(): Promise<void> {
Alert.alert('Logout', 'Are you sure you want to log out?', [
{ text: 'Cancel', style: 'cancel' },
Alert.alert(t('mobile.set.logout'), t('mobile.set.logoutConfirm'), [
{ text: t('mobile.rec.cancel'), style: 'cancel' },
{
text: 'Logout',
text: t('mobile.set.logout'),
style: 'destructive',
onPress: async () => {
await supabase.auth.signOut()
@ -31,14 +40,17 @@ export default function SettingsScreen(): React.ReactElement {
return (
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
{/* Header */}
<PhosphorText variant="title" color="primary" style={[styles.title, { paddingTop: insets.top + 8 }]}>
Settings
<PhosphorText
variant="title"
color="primary"
style={[styles.title, { paddingTop: insets.top + 8 }]}
>
{t('mobile.set.title')}
</PhosphorText>
{/* Account Section */}
<PhosphorText variant="label" color="muted" style={styles.sectionLabel}>
ACCOUNT & PLAN
{t('mobile.set.accountPlan')}
</PhosphorText>
<MetalCard style={styles.section}>
<View style={styles.profileRow}>
@ -50,16 +62,16 @@ export default function SettingsScreen(): React.ReactElement {
{user?.email?.split('@')[0] ?? 'User'}
</PhosphorText>
<PhosphorText variant="label" color="muted">
{user?.email ?? ''}
{user?.email ?? '\u2014'}
</PhosphorText>
</View>
</View>
<View style={[styles.row, { backgroundColor: d3roNativePalette.bg.inset }]}>
<PhosphorText variant="small" color="muted">TIER</PhosphorText>
<View style={[styles.row, styles.rowInset]}>
<PhosphorText variant="small" color="muted">{t('mobile.dash.tier')}</PhosphorText>
<View style={styles.rowRight}>
<Led color="amber" size={6} />
<PhosphorText variant="body" color="amber" style={{ marginLeft: 8 }}>
FREE
<PhosphorText variant="body" color="amber" style={styles.rowValue}>
{t('mobile.dash.free')}
</PhosphorText>
</View>
</View>
@ -67,20 +79,20 @@ export default function SettingsScreen(): React.ReactElement {
{/* Backend Section */}
<PhosphorText variant="label" color="muted" style={styles.sectionLabel}>
BACKEND CONFIG
{t('mobile.set.backendConfig')}
</PhosphorText>
<MetalCard style={styles.section}>
<View style={styles.row}>
<View>
<PhosphorText variant="body" color="primary">LLM Model</PhosphorText>
<PhosphorText variant="label" color="muted">AI processing backend</PhosphorText>
<PhosphorText variant="body" color="primary">{t('mobile.set.llmModel')}</PhosphorText>
<PhosphorText variant="label" color="muted">{t('mobile.set.llmDesc')}</PhosphorText>
</View>
<PhosphorText variant="body" color="amber">CLAUDE</PhosphorText>
</View>
<View style={[styles.row, styles.rowBorder]}>
<View>
<PhosphorText variant="body" color="primary">Cloud STT</PhosphorText>
<PhosphorText variant="label" color="muted">Google Cloud Speech</PhosphorText>
<PhosphorText variant="body" color="primary">{t('mobile.set.cloudStt')}</PhosphorText>
<PhosphorText variant="label" color="muted">{t('mobile.set.cloudSttDesc')}</PhosphorText>
</View>
<Switch
value={true}
@ -95,15 +107,15 @@ export default function SettingsScreen(): React.ReactElement {
{/* Preferences Section */}
<PhosphorText variant="label" color="muted" style={styles.sectionLabel}>
PREFERENCES
{t('mobile.set.preferences')}
</PhosphorText>
<MetalCard style={styles.section}>
<View style={styles.row}>
<PhosphorText variant="body" color="primary">Language</PhosphorText>
<PhosphorText variant="body" color="primary">{t('mobile.set.language')}</PhosphorText>
<PhosphorText variant="body" color="muted">Korean</PhosphorText>
</View>
<View style={[styles.row, styles.rowBorder]}>
<PhosphorText variant="body" color="primary">Auto Polish</PhosphorText>
<PhosphorText variant="body" color="primary">{t('mobile.set.autoPolish')}</PhosphorText>
<Switch
value={true}
trackColor={{
@ -114,7 +126,7 @@ export default function SettingsScreen(): React.ReactElement {
/>
</View>
<View style={[styles.row, styles.rowBorder]}>
<PhosphorText variant="body" color="primary">Haptic Feedback</PhosphorText>
<PhosphorText variant="body" color="primary">{t('mobile.set.haptic')}</PhosphorText>
<Switch
value={true}
trackColor={{
@ -128,16 +140,15 @@ export default function SettingsScreen(): React.ReactElement {
{/* Logout */}
<View style={styles.logoutWrap}>
<PhysicalButton label="LOGOUT" variant="secondary" onPress={() => void handleLogout()} />
<PhysicalButton
label={t('mobile.set.logout')}
variant="secondary"
onPress={() => void handleLogout()}
/>
</View>
{/* Status Footer */}
<View style={styles.footer}>
<Led color="green" size={6} />
<PhosphorText variant="label" color="muted" style={{ marginLeft: 6, letterSpacing: 1 }}>
CLOUD CONNECTED
</PhosphorText>
</View>
<AppStatusBar />
</ScrollView>
)
}
@ -149,8 +160,7 @@ const styles = StyleSheet.create({
paddingHorizontal: 20,
paddingBottom: 12,
borderBottomWidth: 1,
borderBottomColor: 'rgba(46, 46, 50, 0.5)',
fontFamily: undefined
borderBottomColor: d3roNativePalette.border.subtle
},
sectionLabel: {
paddingHorizontal: 24,
@ -169,7 +179,7 @@ const styles = StyleSheet.create({
padding: 16,
gap: 12,
borderBottomWidth: 1,
borderBottomColor: 'rgba(46, 46, 50, 0.5)'
borderBottomColor: d3roNativePalette.border.subtle
},
avatar: {
width: 40,
@ -188,17 +198,12 @@ const styles = StyleSheet.create({
alignItems: 'center',
padding: 16
},
rowInset: { backgroundColor: d3roNativePalette.bg.inset },
rowBorder: {
borderTopWidth: 1,
borderTopColor: 'rgba(46, 46, 50, 0.5)'
borderTopColor: d3roNativePalette.border.subtle
},
rowRight: { flexDirection: 'row', alignItems: 'center' },
logoutWrap: { margin: 20 },
footer: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
opacity: 0.5,
marginTop: 16
}
rowValue: { marginLeft: 8 },
logoutWrap: { margin: 20 }
})