feat(mobile): Phase M-1 모바일 프로젝트 기반 + V3 마스터 플랜
- V3 모바일 마스터 플랜 작성 (docs/v3/00-mobile-master-plan.md) 11개 Phase, 5주 로드맵, 전략 결정사항 확정 - 7개 디자인 HTML 레퍼런스 저장 (docs/v3/designs/) - app.json → app.config.ts 전환 + 환경변수 체계 - 5탭 네비게이션 (DASH/HIST/REC FAB/TALK/SET) - metro.config.js monorepo 격리 (root RN 0.84 충돌 해결) - ui-native 테마 디자인 목업 색상 동기화 - SafeArea 적용 (useSafeAreaInsets) - DEV 로그인 우회 (__DEV__ 전용) - Supabase .env 연동 - iOS 시뮬레이터 검증 완료
This commit is contained in:
parent
ffca07d120
commit
211673bc6c
30 changed files with 15010 additions and 525 deletions
|
|
@ -1,10 +1,26 @@
|
|||
// apps/mobile/app/(tabs)/_layout.tsx
|
||||
// 탭 네비게이션 — Meetings / Record / Profile
|
||||
// 5탭 네비게이션: DASH / HIST / REC(FAB) / TALK / SET
|
||||
|
||||
import { Tabs } from 'expo-router'
|
||||
import { useEffect } from 'react'
|
||||
import { useRouter } from 'expo-router'
|
||||
import { View, StyleSheet, Pressable, Platform } from 'react-native'
|
||||
import { Tabs, useRouter } from 'expo-router'
|
||||
import { useAuth } from '../../lib/auth-context'
|
||||
import { d3roNativePalette } from '@d3ro/ui-native'
|
||||
|
||||
const MONO_FONT = Platform.OS === 'ios' ? 'Menlo' : 'monospace'
|
||||
|
||||
function RecordFAB({ onPress }: { onPress: () => void }): React.ReactElement {
|
||||
return (
|
||||
<Pressable onPress={onPress} style={styles.fab}>
|
||||
<View style={styles.fabInner}>
|
||||
<View style={styles.micIcon}>
|
||||
<View style={styles.micBody} />
|
||||
<View style={styles.micBase} />
|
||||
</View>
|
||||
</View>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
export default function TabsLayout(): React.ReactElement {
|
||||
const router = useRouter()
|
||||
|
|
@ -19,17 +35,169 @@ export default function TabsLayout(): React.ReactElement {
|
|||
return (
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
tabBarStyle: { backgroundColor: '#19191b', borderTopColor: '#2a2a2d' },
|
||||
tabBarActiveTintColor: '#f25b29',
|
||||
tabBarInactiveTintColor: '#8e8e93',
|
||||
headerStyle: { backgroundColor: '#19191b' },
|
||||
headerTintColor: '#f25b29',
|
||||
headerTitleStyle: { fontWeight: '300', letterSpacing: 1 }
|
||||
tabBarStyle: {
|
||||
backgroundColor: 'rgba(25, 25, 27, 0.95)',
|
||||
borderTopColor: d3roNativePalette.border.default,
|
||||
borderTopWidth: 1,
|
||||
height: 80,
|
||||
paddingBottom: 20,
|
||||
paddingTop: 8
|
||||
},
|
||||
tabBarActiveTintColor: d3roNativePalette.accent.amber,
|
||||
tabBarInactiveTintColor: d3roNativePalette.text.muted,
|
||||
tabBarLabelStyle: {
|
||||
fontFamily: MONO_FONT,
|
||||
fontSize: 9,
|
||||
fontWeight: '500',
|
||||
letterSpacing: 0.5
|
||||
},
|
||||
headerStyle: { backgroundColor: d3roNativePalette.bg.app },
|
||||
headerTintColor: d3roNativePalette.accent.amber,
|
||||
headerTitleStyle: { fontWeight: '300', letterSpacing: 1, fontFamily: MONO_FONT },
|
||||
headerShown: false
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen name="meetings" options={{ title: 'Meetings' }} />
|
||||
<Tabs.Screen name="record" options={{ title: 'Record' }} />
|
||||
<Tabs.Screen name="profile" options={{ title: 'Profile' }} />
|
||||
<Tabs.Screen
|
||||
name="dash"
|
||||
options={{
|
||||
title: 'DASH',
|
||||
tabBarIcon: ({ color }: { color: string }) => <TabIcon type="dash" color={color} />
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="history"
|
||||
options={{
|
||||
title: 'HIST',
|
||||
tabBarIcon: ({ color }: { color: string }) => <TabIcon type="hist" color={color} />
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="record"
|
||||
options={{
|
||||
title: '',
|
||||
tabBarIcon: () => <RecordFAB onPress={() => router.push('/(tabs)/record')} />,
|
||||
tabBarLabel: () => null
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="talk"
|
||||
options={{
|
||||
title: 'TALK',
|
||||
tabBarIcon: ({ color }: { color: string }) => <TabIcon type="talk" color={color} />
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="settings"
|
||||
options={{
|
||||
title: 'SET',
|
||||
tabBarIcon: ({ color }: { color: string }) => <TabIcon type="set" color={color} />
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
// 단순 아이콘 대용 (Phase M-2에서 SVG 아이콘으로 교체)
|
||||
function TabIcon({ type, color }: { type: string; color: string }): React.ReactElement {
|
||||
const iconStyles: Record<string, React.ReactElement> = {
|
||||
dash: (
|
||||
<View style={[styles.iconGrid, { borderColor: color }]}>
|
||||
{[0, 1, 2, 3].map((i) => (
|
||||
<View key={i} style={[styles.iconGridCell, { borderColor: color }]} />
|
||||
))}
|
||||
</View>
|
||||
),
|
||||
hist: (
|
||||
<View style={[styles.iconCircle, { borderColor: color }]}>
|
||||
<View style={[styles.iconClockHand, { backgroundColor: color }]} />
|
||||
</View>
|
||||
),
|
||||
talk: (
|
||||
<View style={[styles.iconChat, { borderColor: color }]} />
|
||||
),
|
||||
set: (
|
||||
<View style={[styles.iconGear, { borderColor: color }]} />
|
||||
)
|
||||
}
|
||||
return iconStyles[type] ?? <View />
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
fab: {
|
||||
position: 'relative',
|
||||
top: -20,
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: 28,
|
||||
backgroundColor: d3roNativePalette.accent.amber,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
shadowColor: d3roNativePalette.accent.amber,
|
||||
shadowOffset: { width: 0, height: 0 },
|
||||
shadowOpacity: 0.4,
|
||||
shadowRadius: 10,
|
||||
elevation: 8,
|
||||
borderWidth: 4,
|
||||
borderColor: d3roNativePalette.bg.app
|
||||
},
|
||||
fabInner: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
micIcon: {
|
||||
alignItems: 'center'
|
||||
},
|
||||
micBody: {
|
||||
width: 8,
|
||||
height: 14,
|
||||
borderRadius: 4,
|
||||
backgroundColor: d3roNativePalette.bg.app,
|
||||
marginBottom: 2
|
||||
},
|
||||
micBase: {
|
||||
width: 14,
|
||||
height: 2,
|
||||
borderRadius: 1,
|
||||
backgroundColor: d3roNativePalette.bg.app
|
||||
},
|
||||
// Tab icons (placeholder — Phase M-2에서 SVG로 교체)
|
||||
iconGrid: {
|
||||
width: 22,
|
||||
height: 22,
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: 2
|
||||
},
|
||||
iconGridCell: {
|
||||
width: 9,
|
||||
height: 9,
|
||||
borderWidth: 1.5,
|
||||
borderRadius: 2
|
||||
},
|
||||
iconCircle: {
|
||||
width: 22,
|
||||
height: 22,
|
||||
borderRadius: 11,
|
||||
borderWidth: 1.5,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
iconClockHand: {
|
||||
width: 1.5,
|
||||
height: 7,
|
||||
position: 'absolute',
|
||||
top: 3
|
||||
},
|
||||
iconChat: {
|
||||
width: 22,
|
||||
height: 18,
|
||||
borderWidth: 1.5,
|
||||
borderRadius: 4
|
||||
},
|
||||
iconGear: {
|
||||
width: 22,
|
||||
height: 22,
|
||||
borderWidth: 1.5,
|
||||
borderRadius: 11
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue