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:
216
lib/widgets/themed_text.dart
Normal file
216
lib/widgets/themed_text.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user