전역 구조 리팩터링 및 테스트 확장

This commit is contained in:
JiWoong Sul
2025-09-29 01:51:47 +09:00
parent c00c0c9ab2
commit fef7108479
70 changed files with 7709 additions and 3185 deletions

View File

@@ -0,0 +1,208 @@
import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
/// Superport UI에서 공통으로 사용하는 Shad 테마 정의.
class SuperportShadTheme {
const SuperportShadTheme._();
static const Color primaryColor = Color(0xFF1B4F87);
static const Color successColor = Color(0xFF2E8B57);
static const Color warningColor = Color(0xFFFFC107);
static const Color dangerColor = Color(0xFFDC3545);
static const Color infoColor = Color(0xFF17A2B8);
/// 라이트 모드용 Shad 테마를 반환한다.
static ShadThemeData light() {
return ShadThemeData(
brightness: Brightness.light,
colorScheme: ShadColorScheme(
background: Color(0xFFFFFFFF),
foreground: Color(0xFF09090B),
card: Color(0xFFFFFFFF),
cardForeground: Color(0xFF09090B),
popover: Color(0xFFFFFFFF),
popoverForeground: Color(0xFF09090B),
primary: primaryColor,
primaryForeground: Color(0xFFFAFAFA),
secondary: Color(0xFFF4F4F5),
secondaryForeground: Color(0xFF18181B),
muted: Color(0xFFF4F4F5),
mutedForeground: Color(0xFF71717A),
accent: Color(0xFFF4F4F5),
accentForeground: Color(0xFF18181B),
destructive: Color(0xFFEF4444),
destructiveForeground: Color(0xFFFAFAFA),
border: Color(0xFFE4E4E7),
input: Color(0xFFE4E4E7),
ring: Color(0xFF18181B),
selection: primaryColor,
),
textTheme: ShadTextTheme(
h1: TextStyle(
fontSize: 36,
fontWeight: FontWeight.w700,
letterSpacing: -0.5,
height: 1.3,
),
h2: TextStyle(
fontSize: 30,
fontWeight: FontWeight.w700,
letterSpacing: -0.5,
height: 1.3,
),
h3: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
letterSpacing: -0.5,
height: 1.3,
),
h4: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
letterSpacing: -0.5,
height: 1.3,
),
p: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
letterSpacing: -0.2,
height: 1.6,
),
blockquote: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
fontStyle: FontStyle.italic,
),
table: TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
list: TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
lead: TextStyle(fontSize: 20, fontWeight: FontWeight.w400),
large: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
small: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
muted: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
letterSpacing: -0.2,
height: 1.6,
),
),
radius: const BorderRadius.all(Radius.circular(8)),
);
}
/// 다크 모드용 Shad 테마를 반환한다.
static ShadThemeData dark() {
return ShadThemeData(
brightness: Brightness.dark,
colorScheme: ShadColorScheme(
background: Color(0xFF09090B),
foreground: Color(0xFFFAFAFA),
card: Color(0xFF09090B),
cardForeground: Color(0xFFFAFAFA),
popover: Color(0xFF09090B),
popoverForeground: Color(0xFFFAFAFA),
primary: primaryColor,
primaryForeground: Color(0xFFFAFAFA),
secondary: Color(0xFF27272A),
secondaryForeground: Color(0xFFFAFAFA),
muted: Color(0xFF27272A),
mutedForeground: Color(0xFFA1A1AA),
accent: Color(0xFF27272A),
accentForeground: Color(0xFFFAFAFA),
destructive: Color(0xFF7F1D1D),
destructiveForeground: Color(0xFFFAFAFA),
border: Color(0xFF27272A),
input: Color(0xFF27272A),
ring: Color(0xFFD4D4D8),
selection: primaryColor,
),
textTheme: ShadTextTheme(
h1: TextStyle(
fontSize: 36,
fontWeight: FontWeight.w700,
letterSpacing: -0.5,
height: 1.3,
),
h2: TextStyle(
fontSize: 30,
fontWeight: FontWeight.w700,
letterSpacing: -0.5,
height: 1.3,
),
h3: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
letterSpacing: -0.5,
height: 1.3,
),
h4: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
letterSpacing: -0.5,
height: 1.3,
),
p: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
letterSpacing: -0.2,
height: 1.6,
),
blockquote: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
fontStyle: FontStyle.italic,
),
table: TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
list: TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
lead: TextStyle(fontSize: 20, fontWeight: FontWeight.w400),
large: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
small: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
muted: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
letterSpacing: -0.2,
height: 1.6,
),
),
radius: const BorderRadius.all(Radius.circular(8)),
);
}
/// 상태 텍스트 배경을 위한 데코레이션을 반환한다.
static BoxDecoration statusDecoration(String status) {
Color backgroundColor;
Color borderColor;
switch (status.toLowerCase()) {
case 'active':
case 'success':
backgroundColor = successColor.withValues(alpha: 0.1);
borderColor = successColor;
break;
case 'warning':
case 'pending':
backgroundColor = warningColor.withValues(alpha: 0.1);
borderColor = warningColor;
break;
case 'danger':
case 'error':
backgroundColor = dangerColor.withValues(alpha: 0.1);
borderColor = dangerColor;
break;
case 'info':
backgroundColor = infoColor.withValues(alpha: 0.1);
borderColor = infoColor;
break;
case 'inactive':
case 'disabled':
default:
backgroundColor = Colors.grey.withValues(alpha: 0.1);
borderColor = Colors.grey;
}
return BoxDecoration(
color: backgroundColor,
border: Border.all(color: borderColor, width: 1),
borderRadius: BorderRadius.circular(4),
);
}
}

View File

@@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
/// 전역 테마 모드를 관리하는 컨트롤러.
class ThemeController extends ChangeNotifier {
ThemeController({ThemeMode initialMode = ThemeMode.system})
: _mode = initialMode;
ThemeMode _mode;
ThemeMode get mode => _mode;
void update(ThemeMode mode) {
if (_mode == mode) return;
_mode = mode;
notifyListeners();
}
void cycle() {
switch (_mode) {
case ThemeMode.system:
update(ThemeMode.light);
break;
case ThemeMode.light:
update(ThemeMode.dark);
break;
case ThemeMode.dark:
update(ThemeMode.system);
break;
}
}
}
/// [ThemeController]를 하위 위젯에 제공하는 Inherited 위젯.
class ThemeControllerScope extends InheritedNotifier<ThemeController> {
const ThemeControllerScope({
super.key,
required ThemeController controller,
required super.child,
}) : super(notifier: controller);
static ThemeController of(BuildContext context) {
final scope = context
.dependOnInheritedWidgetOfExactType<ThemeControllerScope>();
assert(scope != null, 'ThemeControllerScope가 위젯 트리에 없습니다.');
return scope!.notifier!;
}
}