Files
submanager/lib/widgets/themed_text.dart
JiWoong Sul 2f60ef585a feat: 글래스모피즘 디자인 시스템 및 색상 가이드 전면 적용
- @doc/color.md 가이드라인에 따른 색상 시스템 전면 개편
- 딥 블루(#2563EB), 스카이 블루(#60A5FA) 메인 컬러로 변경
- 모든 화면과 위젯에 글래스모피즘 효과 일관성 있게 적용
- darkNavy, navyGray 등 새로운 텍스트 색상 체계 도입
- 공통 스낵바 및 다이얼로그 컴포넌트 추가
- Claude AI 프로젝트 컨텍스트 파일(CLAUDE.md) 추가

영향받은 컴포넌트:
- 10개 스크린 (main, settings, detail, splash 등)
- 30개 이상 위젯 (buttons, cards, forms 등)
- 테마 시스템 (AppColors, AppTheme)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-11 18:41:05 +09:00

213 lines
5.3 KiB
Dart

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 AppColors.pureWhite;
if (forceDark) return AppColors.darkNavy;
final brightness = Theme.of(context).brightness;
// 글래스모피즘 환경에서는 배경이 밝으므로 어두운 텍스트 사용
if (_isGlassmorphicContext(context)) {
return AppColors.darkNavy; // color.md 가이드: 밝은 배경 위 어두운 텍스트
}
// 일반 환경
return brightness == Brightness.dark
? AppColors.pureWhite
: AppColors.darkNavy;
}
/// 글래스모피즘 컨텍스트인지 확인
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,
),
);
}
}