JJ_MKVMaker/lib/ui/theme.dart

199 lines
7.2 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'package:flutter/material.dart';
/// 화면 색 한 벌 (어두운 · 밝은)
class JjPalette {
final Brightness brightness;
final Color bg, panel, panelHigh, border, accent, text, textDim, danger, success;
/// 강조색 바탕 위 글자 (버튼 등)
final Color onAccent;
/// 주의 (주황) · 경고 (빨강) · 노랑 · 초록 강조 - 어두운 화면은 예전에 쓰던 Material 색 그대로 (orangeAccent · redAccent · amber · greenAccent)
final Color warning, alert, amber, positive;
/// 노랑을 글자에 쓸 때 (밝은 화면은 [amber] (아이콘 · 폴더 · 별, 대비 3:1) 보다 진하게 - 글자 대비 4.5:1)
final Color amberText;
const JjPalette({
required this.brightness,
required this.bg,
required this.panel,
required this.panelHigh,
required this.border,
required this.accent,
required this.text,
required this.textDim,
required this.danger,
required this.success,
required this.onAccent,
required this.warning,
required this.alert,
required this.amber,
required this.positive,
required this.amberText,
});
/// CapCut 참고 어두운 테마 (처음 값 - 예전 그대로)
static const dark = JjPalette(
brightness: Brightness.dark,
bg: Color(0xFF141417),
panel: Color(0xFF1E1E23),
panelHigh: Color(0xFF28282F),
border: Color(0xFF2E2E36),
accent: Color(0xFF00C4CC), // CapCut 계열 청록
text: Color(0xFFE8E8EC),
textDim: Color(0xFF8E8E99),
danger: Color(0xFFFF5C6C),
success: Color(0xFF3DD68C),
onAccent: Colors.black,
warning: Color(0xFFFFAB40),
alert: Color(0xFFFF5252),
amber: Color(0xFFFFC107),
positive: Color(0xFF69F0AE),
amberText: Color(0xFFFFC107),
);
/// X-plore 비교 5-1 ⑦ 밝은 테마 (관리자 지시 10/21, 시험). 흰 바탕에서 글자 대비 4.5:1 이상이 되게 강조 · 경고 · 성공을 어둡게
static const light = JjPalette(
brightness: Brightness.light,
bg: Color(0xFFF4F4F6),
panel: Color(0xFFFFFFFF),
panelHigh: Color(0xFFE9E9EE),
border: Color(0xFFD3D3DB),
accent: Color(0xFF00747A),
text: Color(0xFF1B1B20),
textDim: Color(0xFF5E5E6A),
danger: Color(0xFFC62833),
success: Color(0xFF186B42),
onAccent: Colors.white,
warning: Color(0xFFA14A00),
alert: Color(0xFFC62828),
amber: Color(0xFFB07800),
positive: Color(0xFF186B42),
amberText: Color(0xFF7A5900),
);
}
/// 앱 전체의 색. 테마 (환경 설정 "화면 밝기") 에 따라 [use] 로 바뀐다 - 그래서 const 로 쓰지 않는다
class JjColors {
static JjPalette _p = JjPalette.dark;
/// 지금 쓰는 색 한 벌
static JjPalette get palette => _p;
static Brightness get brightness => _p.brightness;
static bool get isLight => _p.brightness == Brightness.light;
/// 색 한 벌을 바꾼다 (바꿨으면 true). 화면 전체를 다시 그리는 것은 부르는 쪽 ([rebuildAllElements])
static bool use(Brightness b) {
final next = b == Brightness.light ? JjPalette.light : JjPalette.dark;
if (identical(next, _p)) return false;
_p = next;
return true;
}
static Color get bg => _p.bg;
static Color get panel => _p.panel;
static Color get panelHigh => _p.panelHigh;
static Color get border => _p.border;
static Color get accent => _p.accent;
static Color get text => _p.text;
static Color get textDim => _p.textDim;
static Color get danger => _p.danger;
static Color get success => _p.success;
static Color get onAccent => _p.onAccent;
static Color get warning => _p.warning;
static Color get alert => _p.alert;
static Color get amber => _p.amber;
static Color get positive => _p.positive;
static Color get amberText => _p.amberText;
}
/// 색을 바꾼 뒤 모든 화면 조각을 다시 그린다 (상태는 그대로 - [JjColors] 를 직접 읽는 곳도 새 색으로)
void rebuildAllElements(Element root) {
void mark(Element e) {
e.markNeedsBuild();
e.visitChildren(mark);
}
mark(root);
}
/// 환경 설정 "화면 밝기" 값 ('dark' 처음 값 · 'light' · 'system') → 밝기
Brightness resolveBrightness(String mode, Brightness platform) => switch (mode) {
'light' => Brightness.light,
'system' => platform,
_ => Brightness.dark,
};
ThemeData buildTheme([Brightness? brightness]) {
final c = brightness == null ? JjColors.palette : (brightness == Brightness.light ? JjPalette.light : JjPalette.dark);
final light = c.brightness == Brightness.light;
final base = ThemeData(
brightness: c.brightness,
useMaterial3: true,
colorScheme: light
? ColorScheme.light(primary: c.accent, secondary: c.accent, surface: c.panel, error: c.danger, onPrimary: c.onAccent)
: ColorScheme.dark(primary: c.accent, secondary: c.accent, surface: c.panel, error: c.danger),
scaffoldBackgroundColor: c.bg,
fontFamily: 'Malgun Gothic',
);
return base.copyWith(
// 66-2: 좌우로 밀어 화면을 옮길 때는 그 방향으로 미끄러지게 (그 밖의 화면 열기는 기본 전환)
pageTransitionsTheme: const PageTransitionsTheme(builders: {
TargetPlatform.android: NavSlideTransitionsBuilder(),
TargetPlatform.windows: NavSlideTransitionsBuilder(),
TargetPlatform.linux: NavSlideTransitionsBuilder(),
TargetPlatform.macOS: NavSlideTransitionsBuilder(),
TargetPlatform.iOS: NavSlideTransitionsBuilder(),
}),
dividerColor: c.border,
textTheme: base.textTheme.apply(
bodyColor: c.text,
displayColor: c.text,
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: c.accent,
foregroundColor: c.onAccent,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: c.text,
side: BorderSide(color: c.border),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
),
),
);
}
/// 66-2: 다음 화면으로 열리는 화면이 어느 쪽에서 들어올지 (+1 오른쪽에서 = 다음, −1 왼쪽에서 = 이전, 0 기본 전환).
/// 좌우로 밀기 · 이전 · 다음으로 옮길 때만 정하고, 화면이 열리면 (그 화면에 기억한 뒤) 0 으로 돌린다.
class NavSlide {
static int pending = 0;
static final _dir = Expando<int>('navSlide');
}
class NavSlideTransitionsBuilder extends PageTransitionsBuilder {
const NavSlideTransitionsBuilder();
@override
Widget buildTransitions<T>(PageRoute<T> route, BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
// 처음 그릴 때의 방향을 그 화면에 기억한다 (전환 중에 다시 불려도 같은 방향)
var dir = NavSlide._dir[route];
if (dir == null) {
dir = NavSlide._dir[route] = NavSlide.pending;
NavSlide.pending = 0;
}
if (dir == 0) {
return const PageTransitionsTheme().buildTransitions(route, context, animation, secondaryAnimation, child);
}
return SlideTransition(
position: Tween(begin: Offset(dir.toDouble(), 0), end: Offset.zero)
.animate(CurvedAnimation(parent: animation, curve: Curves.easeOutCubic)),
child: child,
);
}
}