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:
parent
541f04d02b
commit
55eb62189a
19 changed files with 10435 additions and 1073 deletions
|
|
@ -1,137 +1,156 @@
|
|||
// apps/mobile/app/(tabs)/dash.tsx
|
||||
// 대시보드 탭 — 세션 통계, 사용량, 시스템 상태
|
||||
// Phase M-2에서 디자인(docs/v3/designs/dashboard.html) 정밀 적용
|
||||
// Dashboard tab — session stats, usage, system status
|
||||
// Design ref: docs/v3/designs/dashboard.html
|
||||
|
||||
import { View, ScrollView, StyleSheet } from 'react-native'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { MetalCard, PhosphorText, Led, d3roNativePalette } from '@d3ro/ui-native'
|
||||
import {
|
||||
MetalCard,
|
||||
PhosphorText,
|
||||
Led,
|
||||
ScreenPanel,
|
||||
AppStatusBar,
|
||||
Header,
|
||||
d3roNativePalette
|
||||
} from '@d3ro/ui-native'
|
||||
import { useI18n } from '@d3ro/i18n'
|
||||
|
||||
export default function DashScreen(): React.ReactElement {
|
||||
const insets = useSafeAreaInsets()
|
||||
const { t } = useI18n()
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
style={styles.container}
|
||||
contentContainerStyle={[styles.content, { paddingTop: insets.top + 8, paddingBottom: insets.bottom + 100 }]}
|
||||
contentContainerStyle={[styles.content, { paddingBottom: insets.bottom + 100 }]}
|
||||
>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<View style={styles.headerLeft}>
|
||||
<Led color="amber" size={8} />
|
||||
<PhosphorText variant="label" color="muted" style={styles.headerLabel}>
|
||||
D3RO-VOICE
|
||||
</PhosphorText>
|
||||
</View>
|
||||
<View style={styles.headerRight}>
|
||||
<PhosphorText variant="label" color="muted">
|
||||
v1.0.0
|
||||
</PhosphorText>
|
||||
<Led color="green" size={6} />
|
||||
<Led color="amber" size={6} />
|
||||
</View>
|
||||
</View>
|
||||
<Header title={t('mobile.dash.title')} showBorder={false} paddingTop={insets.top} />
|
||||
|
||||
{/* Session Overview */}
|
||||
<MetalCard inset style={styles.overviewCard}>
|
||||
<View style={styles.overviewHeader}>
|
||||
<PhosphorText variant="small" color="muted">
|
||||
SESSION OVERVIEW
|
||||
</PhosphorText>
|
||||
</View>
|
||||
{/* Session Overview — InsetPanel */}
|
||||
<ScreenPanel style={styles.overviewPanel}>
|
||||
<PhosphorText variant="small" color="muted" style={styles.sectionLabel}>
|
||||
{t('mobile.dash.sessionOverview')}
|
||||
</PhosphorText>
|
||||
<View style={styles.bigNumber}>
|
||||
<PhosphorText variant="hero" color="amber">
|
||||
0
|
||||
</PhosphorText>
|
||||
<PhosphorText variant="body" color="muted" style={{ marginLeft: 12 }}>
|
||||
TODAY
|
||||
<PhosphorText variant="hero" color="amber">0</PhosphorText>
|
||||
<PhosphorText variant="body" color="muted" style={styles.todayLabel}>
|
||||
{t('mobile.dash.today')}
|
||||
</PhosphorText>
|
||||
</View>
|
||||
<PhosphorText variant="small" color="muted" style={styles.guideText}>
|
||||
{t('mobile.dash.tapToRecord')}
|
||||
</PhosphorText>
|
||||
<View style={styles.overviewFooter}>
|
||||
<View>
|
||||
<PhosphorText variant="label" color="muted">WORDS</PhosphorText>
|
||||
<PhosphorText variant="label" color="muted">{t('mobile.dash.words')}</PhosphorText>
|
||||
<PhosphorText variant="value" color="amber">0</PhosphorText>
|
||||
</View>
|
||||
<View style={{ alignItems: 'flex-end' }}>
|
||||
<PhosphorText variant="label" color="muted">STREAK</PhosphorText>
|
||||
<View style={styles.alignEnd}>
|
||||
<PhosphorText variant="label" color="muted">{t('mobile.dash.streak')}</PhosphorText>
|
||||
<PhosphorText variant="value" color="amber">0</PhosphorText>
|
||||
</View>
|
||||
</View>
|
||||
</MetalCard>
|
||||
</ScreenPanel>
|
||||
|
||||
{/* 2x2 Stats Grid */}
|
||||
<View style={styles.statsGrid}>
|
||||
<MetalCard style={styles.statCard}>
|
||||
<PhosphorText variant="label" color="muted">REC</PhosphorText>
|
||||
<PhosphorText variant="label" color="muted">{t('mobile.dash.rec')}</PhosphorText>
|
||||
<PhosphorText variant="value" color="amber">0</PhosphorText>
|
||||
<PhosphorText variant="label" color="muted">MIN</PhosphorText>
|
||||
<PhosphorText variant="label" color="muted">{t('mobile.dash.min')}</PhosphorText>
|
||||
</MetalCard>
|
||||
<MetalCard style={styles.statCard}>
|
||||
<PhosphorText variant="label" color="muted">WORDS</PhosphorText>
|
||||
<PhosphorText variant="label" color="muted">{t('mobile.dash.words')}</PhosphorText>
|
||||
<PhosphorText variant="value" color="amber">0</PhosphorText>
|
||||
</MetalCard>
|
||||
<MetalCard style={styles.statCard}>
|
||||
<PhosphorText variant="label" color="muted">TODAY</PhosphorText>
|
||||
<PhosphorText variant="label" color="muted">{t('mobile.dash.today')}</PhosphorText>
|
||||
<PhosphorText variant="value" color="amber">0</PhosphorText>
|
||||
</MetalCard>
|
||||
<MetalCard style={styles.statCard}>
|
||||
<PhosphorText variant="label" color="muted">STREAK</PhosphorText>
|
||||
<PhosphorText variant="label" color="muted">{t('mobile.dash.streak')}</PhosphorText>
|
||||
<PhosphorText variant="value" color="amber">0</PhosphorText>
|
||||
</MetalCard>
|
||||
</View>
|
||||
|
||||
{/* Backend Info */}
|
||||
<MetalCard style={styles.infoCard}>
|
||||
<PhosphorText variant="small" color="muted">BACKEND</PhosphorText>
|
||||
<PhosphorText variant="small" color="muted">{t('mobile.dash.backend')}</PhosphorText>
|
||||
<View style={styles.infoRow}>
|
||||
<Led color="green" size={6} />
|
||||
<PhosphorText variant="body" color="amber" style={{ marginLeft: 8 }}>
|
||||
<PhosphorText variant="body" color="amber" style={styles.infoValue}>
|
||||
CLOUD (CLAUDE)
|
||||
</PhosphorText>
|
||||
</View>
|
||||
</MetalCard>
|
||||
|
||||
<MetalCard style={styles.infoCard}>
|
||||
<PhosphorText variant="small" color="muted">TIER</PhosphorText>
|
||||
<PhosphorText variant="small" color="muted">{t('mobile.dash.tier')}</PhosphorText>
|
||||
<View style={styles.infoRow}>
|
||||
<Led color="amber" size={6} />
|
||||
<PhosphorText variant="body" color="amber" style={{ marginLeft: 8 }}>
|
||||
FREE
|
||||
<PhosphorText variant="body" color="amber" style={styles.infoValue}>
|
||||
{t('mobile.dash.free')}
|
||||
</PhosphorText>
|
||||
</View>
|
||||
</MetalCard>
|
||||
|
||||
{/* Usage */}
|
||||
<PhosphorText variant="label" color="muted" style={styles.usageTitle}>
|
||||
{t('mobile.dash.usage')}
|
||||
</PhosphorText>
|
||||
<MetalCard style={styles.usageCard}>
|
||||
<UsageRow label={t('mobile.dash.dictation')} value={t('mobile.dash.unlimited')} />
|
||||
<View style={styles.usageDivider} />
|
||||
<UsageRow label={t('mobile.dash.llmProcess')} value={t('mobile.dash.unlimited')} />
|
||||
<View style={styles.usageDivider} />
|
||||
<UsageRow label={t('mobile.dash.premiumQuota')} value="250 / 500" progress={0.5} />
|
||||
</MetalCard>
|
||||
|
||||
<AppStatusBar />
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
function UsageRow({
|
||||
label,
|
||||
value,
|
||||
progress
|
||||
}: {
|
||||
label: string
|
||||
value: string
|
||||
progress?: number
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<View style={styles.usageRow}>
|
||||
<PhosphorText variant="body" color="primary">{label}</PhosphorText>
|
||||
<View style={styles.usageRight}>
|
||||
<PhosphorText variant="small" color="amber">{value}</PhosphorText>
|
||||
{progress != null && (
|
||||
<View style={styles.progressBar}>
|
||||
<View style={[styles.progressFill, { width: `${progress * 100}%` }]} />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: d3roNativePalette.bg.app },
|
||||
content: { padding: 20 },
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12
|
||||
},
|
||||
headerLeft: { flexDirection: 'row', alignItems: 'center', gap: 8 },
|
||||
headerRight: { flexDirection: 'row', alignItems: 'center', gap: 8 },
|
||||
headerLabel: { letterSpacing: 3 },
|
||||
overviewCard: {
|
||||
padding: 20,
|
||||
marginBottom: 12
|
||||
},
|
||||
overviewHeader: { marginBottom: 16 },
|
||||
bigNumber: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'baseline',
|
||||
marginBottom: 24
|
||||
},
|
||||
content: { paddingHorizontal: 20 },
|
||||
overviewPanel: { marginBottom: 12 },
|
||||
sectionLabel: { marginBottom: 16 },
|
||||
bigNumber: { flexDirection: 'row', alignItems: 'baseline', marginBottom: 8 },
|
||||
todayLabel: { marginLeft: 12 },
|
||||
guideText: { marginBottom: 20 },
|
||||
overviewFooter: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: 'rgba(255,255,255,0.04)',
|
||||
borderTopColor: d3roNativePalette.border.subtle,
|
||||
paddingTop: 12
|
||||
},
|
||||
alignEnd: { alignItems: 'flex-end' },
|
||||
statsGrid: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
|
|
@ -139,7 +158,7 @@ const styles = StyleSheet.create({
|
|||
marginBottom: 12
|
||||
},
|
||||
statCard: {
|
||||
width: '47%',
|
||||
width: '47%' as unknown as number,
|
||||
alignItems: 'center',
|
||||
paddingVertical: 16,
|
||||
gap: 4
|
||||
|
|
@ -151,5 +170,28 @@ const styles = StyleSheet.create({
|
|||
marginBottom: 12,
|
||||
paddingVertical: 14
|
||||
},
|
||||
infoRow: { flexDirection: 'row', alignItems: 'center' }
|
||||
infoRow: { flexDirection: 'row', alignItems: 'center' },
|
||||
infoValue: { marginLeft: 8 },
|
||||
usageTitle: { letterSpacing: 3, marginBottom: 8, marginLeft: 4 },
|
||||
usageCard: { marginBottom: 12, padding: 0, overflow: 'hidden' },
|
||||
usageRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: 16
|
||||
},
|
||||
usageRight: { alignItems: 'flex-end', gap: 4 },
|
||||
usageDivider: { height: 1, backgroundColor: d3roNativePalette.border.subtle },
|
||||
progressBar: {
|
||||
width: 80,
|
||||
height: 4,
|
||||
backgroundColor: d3roNativePalette.bg.inset,
|
||||
borderRadius: 2,
|
||||
overflow: 'hidden'
|
||||
},
|
||||
progressFill: {
|
||||
height: '100%' as unknown as number,
|
||||
backgroundColor: d3roNativePalette.accent.green,
|
||||
borderRadius: 2
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue