타입 스케일·공통 크롬을 Figma 기준으로 정렬 — 상단 내비게이션 · 하단 탭 아이콘 · 버튼 · 시트

This commit is contained in:
Sn-Kinos
2026-09-27 19:59:33 +09:00
parent 65ba44a1d1
commit 388ea4227c
19 changed files with 153 additions and 69 deletions
+34 -18
View File
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../config/feature_flags.dart';
import '../theme/tokens.dart';
import '../ui/app_icons.dart';
import '../../features/auth/auth_repository.dart';
import '../../features/auth/login_screen.dart';
import '../../features/galaxy/galaxy_screen.dart';
@@ -22,7 +24,9 @@ class AppTab {
/// **계약 고정 문자열.** 한글화·치환·순서 변경 금지.
final String label;
final String path;
final IconData icon;
/// Figma Bottom Navigation 아이콘. 색은 NavigationBar 테마가 입힌다.
final Widget icon;
/// 이 탭을 여는 원격 플래그. null 이면 항상 보인다.
///
@@ -39,24 +43,29 @@ class AppTab {
/// - 시안 `시안/screens/S-*.html` 의 `<nav class="tabbar">` (5개 루트 화면 전부 동일)
///
/// 라벨 문자열과 순서는 스모크 테스트가 정확 일치로 검증한다.
/// 아이콘은 아직 확정 자산이 없어 Material 기본 아이콘을 쓴다(README 「미투입 항목」).
/// 아이콘은 Figma Iconset(house · message-square · badge · circle-user-round)이다.
/// STATION 은 Figma 하단 탭에 없어(1차 비노출) Material 아이콘을 둔다.
///
/// **이 목록은 계약이지 화면이 아니다.** 지금 하단 탭에 실제로 그려지는 것은
/// [visibleTabs] 이고 STATION 은 원격 플래그가 켜질 때까지 빠진다 — 목록에서
/// 지우면 `design/craft-allowlist.yaml` 과 어긋나고, 켜려면 재배포가 필요해진다.
const List<AppTab> appTabs = [
AppTab(label: 'HOME', path: '/home', icon: Icons.home_outlined),
AppTab(label: 'INSIDE', path: '/inside', icon: Icons.chat_bubble_outline),
AppTab(label: 'GALAXY', path: '/galaxy', icon: Icons.auto_awesome_outlined),
AppTab(label: 'HOME', path: '/home', icon: ImageIcon(AppIcons.house)),
AppTab(
label: 'INSIDE',
path: '/inside',
icon: ImageIcon(AppIcons.messageSquare),
),
AppTab(label: 'GALAXY', path: '/galaxy', icon: ImageIcon(AppIcons.badge)),
// 별첨 B v1.4 — *1차에서는 사용자 탭과 Coming Soon 을 모두 비노출하고
// Route/데이터 구조만 유지해 향후 앱 재배포 없이 활성화합니다.*
AppTab(
label: 'STATION',
path: '/station',
icon: Icons.storefront_outlined,
icon: Icon(Icons.storefront_outlined),
gatedBy: _stationGate,
),
AppTab(label: 'MY', path: '/my', icon: Icons.account_circle_outlined),
AppTab(label: 'MY', path: '/my', icon: ImageIcon(AppIcons.circleUserRound)),
];
bool _stationGate(FeatureFlags flags) => flags.stationTab;
@@ -159,18 +168,25 @@ class AppShell extends StatelessWidget {
final selected = branches.indexOf(shell.currentIndex);
return Scaffold(
body: shell,
bottomNavigationBar: NavigationBar(
// 숨긴 탭에 머무는 동안(딥링크 등)에는 선택 표시를 첫 탭으로 둔다.
selectedIndex: selected < 0 ? 0 : selected,
onDestinationSelected: (i) => shell.goBranch(
branches[i],
// initialLocation: true 를 주면 이미 열려 있는 탭을 다시 누를 때 루트로 되돌아간다.
initialLocation: branches[i] == shell.currentIndex,
// Figma 탭 줄은 좌우 16 을 비우고 칸을 나눈다(350:215853).
bottomNavigationBar: ColoredBox(
color: AppColors.cosmicVoid,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: NavigationBar(
// 숨긴 탭에 머무는 동안(딥링크 등)에는 선택 표시를 첫 탭으로 둔다.
selectedIndex: selected < 0 ? 0 : selected,
onDestinationSelected: (i) => shell.goBranch(
branches[i],
// initialLocation: true 를 주면 이미 열려 있는 탭을 다시 누를 때 루트로 되돌아간다.
initialLocation: branches[i] == shell.currentIndex,
),
destinations: [
for (final tab in tabs)
NavigationDestination(icon: tab.icon, label: tab.label),
],
),
),
destinations: [
for (final tab in tabs)
NavigationDestination(icon: Icon(tab.icon), label: tab.label),
],
),
);
},