feat(mobile): Expo → RN CLI 전환 + Android 빌드 성공

RN 0.85 + React 19 기반 apps/mobile-rn 프로젝트 생성.
6개 화면 이식 (Login, Dash, History, Record, Talk, Settings).
@react-navigation/native + bottom-tabs 네비게이션 구성.
Windows NDK CMake libc++_shared 링크 버그 워크아라운드 포함.
Galaxy Fold (SM_F956N) 무선 디버깅 APK 설치 확인.
This commit is contained in:
yunchan8804 2026-04-13 21:13:40 +09:00
parent 55eb62189a
commit bf36a9d97d
68 changed files with 13955 additions and 9375 deletions

View file

@ -0,0 +1,210 @@
// apps/mobile-rn/src/screens/SettingsScreen.tsx
// Settings tab — account, backend, preferences
// Converted from Expo → RN CLI
import React from 'react'
import { View, ScrollView, StyleSheet, Alert, Switch } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
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(t('mobile.set.logout'), t('mobile.set.logoutConfirm'), [
{ text: t('mobile.rec.cancel'), style: 'cancel' },
{
text: t('mobile.set.logout'),
style: 'destructive',
onPress: async () => {
await supabase.auth.signOut()
}
}
])
}
const initials = user?.email
? user.email.substring(0, 2).toUpperCase()
: 'US'
return (
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
<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}>
{t('mobile.set.accountPlan')}
</PhosphorText>
<MetalCard style={styles.section}>
<View style={styles.profileRow}>
<View style={styles.avatar}>
<PhosphorText variant="body" color="amber">{initials}</PhosphorText>
</View>
<View style={styles.profileInfo}>
<PhosphorText variant="body" color="primary">
{user?.email?.split('@')[0] ?? 'User'}
</PhosphorText>
<PhosphorText variant="label" color="muted">
{user?.email ?? '\u2014'}
</PhosphorText>
</View>
</View>
<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={styles.rowValue}>
{t('mobile.dash.free')}
</PhosphorText>
</View>
</View>
</MetalCard>
{/* Backend Section */}
<PhosphorText variant="label" color="muted" style={styles.sectionLabel}>
{t('mobile.set.backendConfig')}
</PhosphorText>
<MetalCard style={styles.section}>
<View style={styles.row}>
<View>
<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">{t('mobile.set.cloudStt')}</PhosphorText>
<PhosphorText variant="label" color="muted">{t('mobile.set.cloudSttDesc')}</PhosphorText>
</View>
<Switch
value={true}
trackColor={{
false: d3roNativePalette.bg.inset,
true: d3roNativePalette.accent.amber
}}
thumbColor="#ffffff"
/>
</View>
</MetalCard>
{/* Preferences Section */}
<PhosphorText variant="label" color="muted" style={styles.sectionLabel}>
{t('mobile.set.preferences')}
</PhosphorText>
<MetalCard style={styles.section}>
<View style={styles.row}>
<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">{t('mobile.set.autoPolish')}</PhosphorText>
<Switch
value={true}
trackColor={{
false: d3roNativePalette.bg.inset,
true: d3roNativePalette.accent.amber
}}
thumbColor="#ffffff"
/>
</View>
<View style={[styles.row, styles.rowBorder]}>
<PhosphorText variant="body" color="primary">{t('mobile.set.haptic')}</PhosphorText>
<Switch
value={true}
trackColor={{
false: d3roNativePalette.bg.inset,
true: d3roNativePalette.accent.amber
}}
thumbColor="#ffffff"
/>
</View>
</MetalCard>
{/* Logout */}
<View style={styles.logoutWrap}>
<PhysicalButton
label={t('mobile.set.logout')}
variant="secondary"
onPress={() => void handleLogout()}
/>
</View>
{/* Status Footer */}
<AppStatusBar />
</ScrollView>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: d3roNativePalette.bg.app },
content: { paddingBottom: 120 },
title: {
paddingHorizontal: 20,
paddingBottom: 12,
borderBottomWidth: 1,
borderBottomColor: d3roNativePalette.border.subtle
},
sectionLabel: {
paddingHorizontal: 24,
paddingTop: 20,
paddingBottom: 8,
letterSpacing: 3
},
section: {
marginHorizontal: 20,
padding: 0,
overflow: 'hidden'
},
profileRow: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
gap: 12,
borderBottomWidth: 1,
borderBottomColor: d3roNativePalette.border.subtle
},
avatar: {
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: d3roNativePalette.bg.inset,
borderWidth: 1,
borderColor: d3roNativePalette.border.default,
justifyContent: 'center',
alignItems: 'center'
},
profileInfo: { gap: 2 },
row: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16
},
rowInset: { backgroundColor: d3roNativePalette.bg.inset },
rowBorder: {
borderTopWidth: 1,
borderTopColor: d3roNativePalette.border.subtle
},
rowRight: { flexDirection: 'row', alignItems: 'center' },
rowValue: { marginLeft: 8 },
logoutWrap: { margin: 20 }
})