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:
JiWoong Sul
2025-07-10 18:36:57 +09:00
parent 8619e96739
commit 4731288622
55 changed files with 8219 additions and 2149 deletions

View File

@@ -0,0 +1,216 @@
import 'package:flutter/material.dart';
import '../theme/app_colors.dart';
/// 배경에 따라 자동으로 색상 대비를 조정하는 텍스트 위젯
class ThemedText extends StatelessWidget {
final String text;
final TextStyle? style;
final TextAlign? textAlign;
final TextOverflow? overflow;
final int? maxLines;
final bool softWrap;
final bool forceLight;
final bool forceDark;
final double? opacity;
final double? fontSize;
final FontWeight? fontWeight;
final double? letterSpacing;
final Color? color;
const ThemedText(
this.text, {
super.key,
this.style,
this.textAlign,
this.overflow,
this.maxLines,
this.softWrap = true,
this.forceLight = false,
this.forceDark = false,
this.opacity,
this.fontSize,
this.fontWeight,
this.letterSpacing,
this.color,
});
/// 배경 밝기에 따른 텍스트 색상 결정
static Color getContrastColor(BuildContext context, {
bool forceLight = false,
bool forceDark = false,
}) {
if (forceLight) return Colors.white;
if (forceDark) return AppColors.textPrimary;
final brightness = Theme.of(context).brightness;
final backgroundColor = Theme.of(context).scaffoldBackgroundColor;
// 글래스모피즘 환경에서는 보통 어두운 배경 위에 밝은 텍스트
if (_isGlassmorphicContext(context)) {
return brightness == Brightness.dark
? Colors.white.withValues(alpha: 0.95)
: AppColors.textPrimary;
}
// 일반 환경
return brightness == Brightness.dark
? Colors.white
: AppColors.textPrimary;
}
/// 글래스모피즘 컨텍스트인지 확인
static bool _isGlassmorphicContext(BuildContext context) {
// 부모 위젯 체인에서 글래스모피즘 카드가 있는지 확인
final glassmorphic = context.findAncestorWidgetOfExactType<GlassmorphicIndicator>();
return glassmorphic != null;
}
@override
Widget build(BuildContext context) {
final textColor = color ?? getContrastColor(
context,
forceLight: forceLight,
forceDark: forceDark,
);
final finalColor = opacity != null
? textColor.withValues(alpha: opacity!)
: textColor;
final defaultStyle = DefaultTextStyle.of(context).style;
// 개별 스타일 속성들을 병합
final baseStyle = TextStyle(
fontSize: fontSize,
fontWeight: fontWeight,
letterSpacing: letterSpacing,
color: finalColor,
);
final effectiveStyle = defaultStyle.merge(baseStyle).merge(style);
return Text(
text,
style: effectiveStyle,
textAlign: textAlign,
overflow: overflow,
maxLines: maxLines,
softWrap: softWrap,
);
}
/// 제목용 스타일 팩토리
static ThemedText headline({
required String text,
TextStyle? style,
bool forceLight = false,
bool forceDark = false,
}) {
return ThemedText(
text,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
).merge(style),
forceLight: forceLight,
forceDark: forceDark,
);
}
/// 부제목용 스타일 팩토리
static ThemedText subtitle({
required String text,
TextStyle? style,
bool forceLight = false,
bool forceDark = false,
double opacity = 0.8,
}) {
return ThemedText(
text,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
).merge(style),
forceLight: forceLight,
forceDark: forceDark,
opacity: opacity,
);
}
/// 본문용 스타일 팩토리
static ThemedText body({
required String text,
TextStyle? style,
bool forceLight = false,
bool forceDark = false,
double opacity = 0.9,
}) {
return ThemedText(
text,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
).merge(style),
forceLight: forceLight,
forceDark: forceDark,
opacity: opacity,
);
}
/// 캡션용 스타일 팩토리
static ThemedText caption({
required String text,
TextStyle? style,
bool forceLight = false,
bool forceDark = false,
double opacity = 0.7,
}) {
return ThemedText(
text,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
).merge(style),
forceLight: forceLight,
forceDark: forceDark,
opacity: opacity,
);
}
}
/// 글래스모피즘 컨텍스트를 표시하는 마커 위젯
class GlassmorphicIndicator extends InheritedWidget {
const GlassmorphicIndicator({
super.key,
required super.child,
});
static GlassmorphicIndicator? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<GlassmorphicIndicator>();
}
@override
bool updateShouldNotify(GlassmorphicIndicator oldWidget) => false;
}
/// 글래스모피즘 환경에서 텍스트 색상을 자동 조정하는 래퍼
class GlassmorphicTextWrapper extends StatelessWidget {
final Widget child;
const GlassmorphicTextWrapper({
super.key,
required this.child,
});
@override
Widget build(BuildContext context) {
return GlassmorphicIndicator(
child: DefaultTextStyle(
style: DefaultTextStyle.of(context).style.copyWith(
color: ThemedText.getContrastColor(context),
),
child: child,
),
);
}
}