106 lines
4.4 KiB
Dart
106 lines
4.4 KiB
Dart
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)];
|