Major UI/UX and architecture improvements
- Implemented new navigation system with NavigationProvider and route management - Added adaptive theme system with ThemeProvider for better theme handling - Introduced glassmorphism design elements (app bars, scaffolds, cards) - Added advanced animations (spring animations, page transitions, staggered lists) - Implemented performance optimizations (memory manager, lazy loading) - Refactored Analysis screen into modular components - Added floating navigation bar with haptic feedback - Improved subscription cards with swipe actions - Enhanced skeleton loading with better animations - Added cached network image support - Improved overall app architecture and code organization 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
379
lib/theme/adaptive_theme.dart
Normal file
379
lib/theme/adaptive_theme.dart
Normal file
@@ -0,0 +1,379 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'app_colors.dart';
|
||||
import 'app_theme.dart';
|
||||
|
||||
/// 적응형 테마 관리 클래스
|
||||
class AdaptiveTheme {
|
||||
/// 라이트 테마
|
||||
static ThemeData get lightTheme => AppTheme.lightTheme;
|
||||
|
||||
/// 다크 테마
|
||||
static ThemeData get darkTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: ColorScheme.dark(
|
||||
primary: AppColors.primaryColor,
|
||||
onPrimary: Colors.white,
|
||||
secondary: AppColors.secondaryColor,
|
||||
tertiary: AppColors.infoColor,
|
||||
error: AppColors.dangerColor,
|
||||
background: const Color(0xFF121212),
|
||||
surface: const Color(0xFF1E1E1E),
|
||||
),
|
||||
|
||||
scaffoldBackgroundColor: const Color(0xFF121212),
|
||||
|
||||
cardTheme: CardTheme(
|
||||
color: const Color(0xFF1E1E1E),
|
||||
elevation: 2,
|
||||
shadowColor: Colors.black.withValues(alpha: 0.3),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: BorderSide(color: Colors.white.withValues(alpha: 0.1), width: 0.5),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
),
|
||||
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: const Color(0xFF1E1E1E),
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
centerTitle: false,
|
||||
titleTextStyle: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.2,
|
||||
),
|
||||
iconTheme: IconThemeData(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
|
||||
textTheme: TextTheme(
|
||||
headlineLarge: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.5,
|
||||
height: 1.2,
|
||||
),
|
||||
headlineMedium: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.5,
|
||||
height: 1.2,
|
||||
),
|
||||
headlineSmall: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.25,
|
||||
height: 1.3,
|
||||
),
|
||||
titleLarge: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.2,
|
||||
height: 1.4,
|
||||
),
|
||||
titleMedium: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.1,
|
||||
height: 1.4,
|
||||
),
|
||||
titleSmall: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0,
|
||||
height: 1.4,
|
||||
),
|
||||
bodyLarge: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: 0.1,
|
||||
height: 1.5,
|
||||
),
|
||||
bodyMedium: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.7),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: 0.1,
|
||||
height: 1.5,
|
||||
),
|
||||
bodySmall: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.5),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: 0.2,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: const Color(0xFF2A2A2A),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: Colors.white.withValues(alpha: 0.1), width: 1),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: AppColors.primaryColor, width: 1.5),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: AppColors.dangerColor, width: 1),
|
||||
),
|
||||
labelStyle: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.7),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.5),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size(0, 48),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
elevation: 0,
|
||||
),
|
||||
),
|
||||
|
||||
dividerTheme: DividerThemeData(
|
||||
color: Colors.white.withValues(alpha: 0.1),
|
||||
thickness: 1,
|
||||
space: 16,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// OLED 최적화 다크 테마
|
||||
static ThemeData get oledTheme {
|
||||
return darkTheme.copyWith(
|
||||
scaffoldBackgroundColor: Colors.black,
|
||||
colorScheme: darkTheme.colorScheme.copyWith(
|
||||
background: Colors.black,
|
||||
surface: const Color(0xFF0A0A0A),
|
||||
),
|
||||
cardTheme: darkTheme.cardTheme.copyWith(
|
||||
color: const Color(0xFF0A0A0A),
|
||||
),
|
||||
appBarTheme: darkTheme.appBarTheme.copyWith(
|
||||
backgroundColor: Colors.black,
|
||||
),
|
||||
inputDecorationTheme: darkTheme.inputDecorationTheme.copyWith(
|
||||
fillColor: const Color(0xFF0A0A0A),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 고대비 테마
|
||||
static ThemeData get highContrastTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
colorScheme: const ColorScheme.highContrastLight(
|
||||
primary: Colors.black,
|
||||
secondary: Colors.black87,
|
||||
tertiary: Colors.black54,
|
||||
error: Colors.red,
|
||||
background: Colors.white,
|
||||
surface: Colors.white,
|
||||
),
|
||||
|
||||
textTheme: const TextTheme(
|
||||
headlineLarge: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
headlineMedium: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
headlineSmall: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
bodyLarge: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
bodyMedium: TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
|
||||
cardTheme: CardTheme(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: const BorderSide(color: Colors.black, width: 2),
|
||||
),
|
||||
),
|
||||
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.black,
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.black, width: 2),
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 시스템 테마에 따른 상태바 스타일 적용
|
||||
static void applySystemUIOverlay(BuildContext context) {
|
||||
final brightness = Theme.of(context).brightness;
|
||||
final isOled = Theme.of(context).scaffoldBackgroundColor == Colors.black;
|
||||
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: brightness == Brightness.dark
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
statusBarBrightness: brightness == Brightness.dark
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
systemNavigationBarColor: isOled
|
||||
? Colors.black
|
||||
: (brightness == Brightness.dark
|
||||
? const Color(0xFF121212)
|
||||
: Colors.white),
|
||||
systemNavigationBarIconBrightness: brightness == Brightness.dark
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
));
|
||||
}
|
||||
|
||||
/// 접근성 설정에 따른 테마 조정
|
||||
static ThemeData getAccessibleTheme(
|
||||
ThemeData baseTheme, {
|
||||
required bool largeText,
|
||||
required bool reduceMotion,
|
||||
required bool highContrast,
|
||||
}) {
|
||||
if (highContrast) {
|
||||
return highContrastTheme;
|
||||
}
|
||||
|
||||
ThemeData theme = baseTheme;
|
||||
|
||||
if (largeText) {
|
||||
theme = theme.copyWith(
|
||||
textTheme: theme.textTheme.apply(
|
||||
fontSizeFactor: 1.2,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (reduceMotion) {
|
||||
theme = theme.copyWith(
|
||||
pageTransitionsTheme: const PageTransitionsTheme(
|
||||
builders: {
|
||||
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
|
||||
TargetPlatform.iOS: FadeUpwardsPageTransitionsBuilder(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return theme;
|
||||
}
|
||||
}
|
||||
|
||||
/// 테마 모드 열거형
|
||||
enum AppThemeMode {
|
||||
light,
|
||||
dark,
|
||||
oled,
|
||||
system,
|
||||
}
|
||||
|
||||
/// 테마 설정 클래스
|
||||
class ThemeSettings {
|
||||
final AppThemeMode mode;
|
||||
final bool useSystemColors;
|
||||
final bool largeText;
|
||||
final bool reduceMotion;
|
||||
final bool highContrast;
|
||||
|
||||
const ThemeSettings({
|
||||
this.mode = AppThemeMode.system,
|
||||
this.useSystemColors = false,
|
||||
this.largeText = false,
|
||||
this.reduceMotion = false,
|
||||
this.highContrast = false,
|
||||
});
|
||||
|
||||
ThemeSettings copyWith({
|
||||
AppThemeMode? mode,
|
||||
bool? useSystemColors,
|
||||
bool? largeText,
|
||||
bool? reduceMotion,
|
||||
bool? highContrast,
|
||||
}) {
|
||||
return ThemeSettings(
|
||||
mode: mode ?? this.mode,
|
||||
useSystemColors: useSystemColors ?? this.useSystemColors,
|
||||
largeText: largeText ?? this.largeText,
|
||||
reduceMotion: reduceMotion ?? this.reduceMotion,
|
||||
highContrast: highContrast ?? this.highContrast,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'mode': mode.name,
|
||||
'useSystemColors': useSystemColors,
|
||||
'largeText': largeText,
|
||||
'reduceMotion': reduceMotion,
|
||||
'highContrast': highContrast,
|
||||
};
|
||||
|
||||
factory ThemeSettings.fromJson(Map<String, dynamic> json) {
|
||||
return ThemeSettings(
|
||||
mode: AppThemeMode.values.firstWhere(
|
||||
(mode) => mode.name == json['mode'],
|
||||
orElse: () => AppThemeMode.system,
|
||||
),
|
||||
useSystemColors: json['useSystemColors'] ?? false,
|
||||
largeText: json['largeText'] ?? false,
|
||||
reduceMotion: json['reduceMotion'] ?? false,
|
||||
highContrast: json['highContrast'] ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -46,4 +46,49 @@ class AppColors {
|
||||
Color(0xFFF43F5E),
|
||||
Color(0xFFE11D48)
|
||||
];
|
||||
|
||||
// Glassmorphism 효과를 위한 색상
|
||||
static const glassSurface = Color(0x0FFFFFFF); // 매우 연한 흰색 (6% opacity)
|
||||
static const glassBackground = Color(0x1AFFFFFF); // 연한 흰색 (10% opacity)
|
||||
static const glassCard = Color(0x33FFFFFF); // 반투명 흰색 (20% opacity)
|
||||
static const glassBorder = Color(0x4DFFFFFF); // 반투명 테두리 (30% opacity)
|
||||
static const glassOverlay = Color(0x0D000000); // 연한 검정 오버레이 (5% opacity)
|
||||
|
||||
// 다크 모드용 Glassmorphism 색상
|
||||
static const glassSurfaceDark = Color(0x0F000000); // 매우 연한 검정 (6% opacity)
|
||||
static const glassBackgroundDark = Color(0x1A000000); // 연한 검정 (10% opacity)
|
||||
static const glassCardDark = Color(0x33000000); // 반투명 검정 (20% opacity)
|
||||
static const glassBorderDark = Color(0x4D000000); // 반투명 검정 테두리 (30% opacity)
|
||||
|
||||
// 백드롭 블러 효과를 위한 그라디언트
|
||||
static const List<Color> glassGradient = [
|
||||
Color(0x1AFFFFFF), // 10% white
|
||||
Color(0x0FFFFFFF), // 6% white
|
||||
];
|
||||
|
||||
static const List<Color> glassGradientDark = [
|
||||
Color(0x1A000000), // 10% black
|
||||
Color(0x0F000000), // 6% black
|
||||
];
|
||||
|
||||
// 시간대별 배경 그라디언트
|
||||
static const List<Color> morningGradient = [
|
||||
Color(0xFFFED7AA), // 따뜻한 오렌지
|
||||
Color(0xFFFBBF24), // 부드러운 노랑
|
||||
];
|
||||
|
||||
static const List<Color> dayGradient = [
|
||||
Color(0xFFDDEAFC), // 연한 하늘색
|
||||
Color(0xFFBFDBFE), // 맑은 파랑
|
||||
];
|
||||
|
||||
static const List<Color> eveningGradient = [
|
||||
Color(0xFFFCA5A5), // 부드러운 핑크
|
||||
Color(0xFFC084FC), // 연한 보라
|
||||
];
|
||||
|
||||
static const List<Color> nightGradient = [
|
||||
Color(0xFF4338CA), // 깊은 인디고
|
||||
Color(0xFF1E1B4B), // 다크 네이비
|
||||
];
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class AppTheme {
|
||||
cardTheme: CardTheme(
|
||||
color: AppColors.cardColor,
|
||||
elevation: 1,
|
||||
shadowColor: Colors.black.withOpacity(0.04),
|
||||
shadowColor: Colors.black.withValues(alpha: 0.04),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: BorderSide(color: AppColors.borderColor, width: 0.5),
|
||||
@@ -265,7 +265,7 @@ class AppTheme {
|
||||
}),
|
||||
trackColor: MaterialStateProperty.resolveWith<Color>((states) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return AppColors.primaryColor.withOpacity(0.5);
|
||||
return AppColors.primaryColor.withValues(alpha: 0.5);
|
||||
}
|
||||
return AppColors.borderColor;
|
||||
}),
|
||||
@@ -300,7 +300,7 @@ class AppTheme {
|
||||
activeTrackColor: AppColors.primaryColor,
|
||||
inactiveTrackColor: AppColors.borderColor,
|
||||
thumbColor: AppColors.primaryColor,
|
||||
overlayColor: AppColors.primaryColor.withOpacity(0.2),
|
||||
overlayColor: AppColors.primaryColor.withValues(alpha: 0.2),
|
||||
trackHeight: 4,
|
||||
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 10),
|
||||
overlayShape: const RoundSliderOverlayShape(overlayRadius: 20),
|
||||
|
||||
Reference in New Issue
Block a user