Flutter 앱 골격 · 5탭 네비게이션 · 로그인 화면 · Firestore 보안 규칙·인덱스
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'tokens.dart';
|
||||
|
||||
/// design-tokens.md → ThemeData 조립.
|
||||
///
|
||||
/// 값은 전부 [tokens.dart] 에서 온다. 여기서 색·크기를 새로 만들면 시안과 어긋난다.
|
||||
/// 서피스는 코스믹 보이드(순수 검정)다 — 결정 D-19, `design/아트디렉션-예외.md`.
|
||||
///
|
||||
/// 컬러 운용은 60-30-10 이고 악센트는 행동·선택·포커스에만 쓴다.
|
||||
/// ColorScheme 슬롯에 네온을 꽂아 뒀다고 해서 화면에서 네 색을 다 풀면 안 된다 —
|
||||
/// 화면당 네온은 하나를 주로 하고 나머지는 보조로만(design-tokens.md 「컬러 운용 규율」).
|
||||
ThemeData buildAppTheme() {
|
||||
const scheme = ColorScheme.dark(
|
||||
primary: AppColors.cosmicAccent,
|
||||
onPrimary: AppColors.onCosmicAccent,
|
||||
secondary: AppColors.neonViolet,
|
||||
onSecondary: AppColors.onNeonViolet,
|
||||
tertiary: AppColors.neonCyan,
|
||||
onTertiary: AppColors.onNeonCyan,
|
||||
error: AppColors.error,
|
||||
onError: AppColors.onError,
|
||||
surface: AppColors.cosmicVoid,
|
||||
onSurface: AppColors.onCosmic,
|
||||
onSurfaceVariant: AppColors.onCosmicVariant,
|
||||
surfaceContainerLowest: AppColors.cosmicVoid,
|
||||
surfaceContainerLow: AppColors.cosmicSurface,
|
||||
surfaceContainer: AppColors.cosmicSurfaceContainer,
|
||||
surfaceContainerHigh: AppColors.cosmicSurfaceRaised,
|
||||
surfaceContainerHighest: AppColors.cosmicSurfaceRaised,
|
||||
outline: AppColors.cosmicOutline,
|
||||
outlineVariant: AppColors.cosmicOutline,
|
||||
);
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: scheme,
|
||||
scaffoldBackgroundColor: AppColors.cosmicVoid,
|
||||
textTheme: _textTheme(scheme),
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: AppColors.cosmicVoid,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
centerTitle: true,
|
||||
),
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
backgroundColor: AppColors.cosmicSurface,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
indicatorColor: AppColors.cosmicSurfaceRaised,
|
||||
// 탭 항목 높이는 touch-min-app(48) 아래로 내리지 않는다.
|
||||
height: AppSizing.touchMinApp + AppSpace.s5,
|
||||
labelTextStyle: WidgetStateProperty.resolveWith(
|
||||
(states) => _style(
|
||||
AppType.caption,
|
||||
states.contains(WidgetState.selected)
|
||||
? AppColors.cosmicAccent
|
||||
: AppColors.onCosmicVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
dividerTheme: const DividerThemeData(color: AppColors.cosmicOutline, thickness: 1),
|
||||
filledButtonTheme: FilledButtonThemeData(
|
||||
style: FilledButton.styleFrom(
|
||||
minimumSize: const Size.fromHeight(AppSizing.touchMinApp),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
),
|
||||
// 글자색은 여기서 정하지 않는다 — foregroundColor 가 결정한다.
|
||||
// textStyle 에 색을 넣으면 소셜 버튼처럼 제공자별 색을 쓰는 곳과 지정이 두 곳으로 갈린다.
|
||||
textStyle: _styleNoColor(AppType.titleM),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TextTheme _textTheme(ColorScheme scheme) => TextTheme(
|
||||
displayLarge: _style(AppType.displayL, scheme.onSurface),
|
||||
displayMedium: _style(AppType.displayM, scheme.onSurface),
|
||||
headlineSmall: _style(AppType.titleL, scheme.onSurface),
|
||||
titleLarge: _style(AppType.titleL, scheme.onSurface),
|
||||
titleMedium: _style(AppType.titleM, scheme.onSurface),
|
||||
bodyLarge: _style(AppType.bodyL, scheme.onSurface),
|
||||
bodyMedium: _style(AppType.bodyM, scheme.onSurface),
|
||||
bodySmall: _style(AppType.bodyS, scheme.onSurfaceVariant),
|
||||
labelLarge: _style(AppType.labelL, scheme.onSurface),
|
||||
labelMedium: _style(AppType.eyebrow, scheme.onSurfaceVariant),
|
||||
labelSmall: _style(AppType.caption, scheme.onSurfaceVariant),
|
||||
);
|
||||
|
||||
/// [TypeToken] → [TextStyle].
|
||||
///
|
||||
/// 폰트 파일 미투입 상태에서는 fontFamily 가 매칭되지 않아 시스템 폰트로 폴백된다.
|
||||
/// 크기·굵기·행간·자간은 그대로 적용되므로 레이아웃은 투입 전후가 거의 같다.
|
||||
TextStyle _style(TypeToken t, Color color) => _styleNoColor(t).copyWith(color: color);
|
||||
|
||||
/// 색 없는 타입 스타일. 색을 다른 속성(foregroundColor 등)이 정하는 자리에 쓴다.
|
||||
TextStyle _styleNoColor(TypeToken t) => TextStyle(
|
||||
fontFamily: t.family,
|
||||
fontSize: t.size,
|
||||
fontWeight: _weight(t.weight),
|
||||
height: t.height,
|
||||
letterSpacing: t.letterSpacing,
|
||||
);
|
||||
|
||||
FontWeight _weight(int value) =>
|
||||
FontWeight.values[(value ~/ 100 - 1).clamp(0, FontWeight.values.length - 1)];
|
||||
Reference in New Issue
Block a user