import 'package:flutter/material.dart'; import '../../../core/theme/tokens.dart'; import '../home_data.dart'; import '../home_screen.dart' show formatPop; import '../../../core/i18n/app_locale.dart'; import '../../quest/widgets/time_attack_countdown.dart'; /// TODAY'S QUEST 한 줄 — Figma 「오늘의 출석체크 / 출석 완료 / +{보상} / 보상받기」. /// /// 보상 숫자는 Figma 의 50 이 아니라 서버 지급액과 같은 원천에서 온다. /// /// 상태별 오른쪽 버튼이 다르다. 정책 설계서 §P-05 — 조건 미충족 시 보상은 /// 지급하지 않는다(`E-QST-01`). class QuestRow extends StatelessWidget { const QuestRow({super.key, required this.quest, this.onTap}); final QuestSummary quest; final VoidCallback? onTap; @override Widget build(BuildContext context) { final t = Theme.of(context).textTheme; return Material( color: AppColors.cosmicSurfaceContainer, borderRadius: BorderRadius.circular(AppRadius.md), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(AppRadius.md), child: Padding( padding: const EdgeInsets.all(AppSpace.s4), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(quest.title, style: t.bodyMedium), const SizedBox(height: AppSpace.s1), Text(_subtitle(quest), style: t.bodySmall), ], ), ), const SizedBox(width: AppSpace.s3), Text( _reward(quest), style: t.titleMedium?.copyWith(color: AppColors.cosmicAccent), ), const SizedBox(width: AppSpace.s3), _Action(state: quest.state, onTap: onTap), ], ), ), ), ); } static String _subtitle(QuestSummary q) => switch (q.state) { QuestState.claimable => appL10n.questAttended, QuestState.inProgress => q.progressLabel ?? appL10n.questInProgressSpaced, QuestState.done => appL10n.questRewardPaid, }; } class _Action extends StatelessWidget { const _Action({required this.state, this.onTap}); final QuestState state; final VoidCallback? onTap; @override Widget build(BuildContext context) { final t = Theme.of(context).textTheme; return switch (state) { QuestState.claimable => FilledButton( onPressed: onTap, style: FilledButton.styleFrom( minimumSize: const Size(0, AppSizing.touchMinApp), padding: const EdgeInsets.symmetric(horizontal: AppSpace.s4), backgroundColor: AppColors.cosmicAccent, foregroundColor: AppColors.onCosmicAccent, ), child: Text(_claimLabel), ), QuestState.inProgress => OutlinedButton( onPressed: onTap, style: OutlinedButton.styleFrom( minimumSize: const Size(0, AppSizing.touchMinApp), padding: const EdgeInsets.symmetric(horizontal: AppSpace.s4), foregroundColor: AppColors.onCosmic, side: const BorderSide(color: AppColors.cosmicOutline), ), child: Text(_startLabel), ), QuestState.done => Padding( padding: const EdgeInsets.symmetric(horizontal: AppSpace.s2), child: Text(_doneLabel, style: t.bodySmall), ), }; } } /// 사본 HOME 「TODAY'S QUEST」 버튼 문구(`252:159354` spec). 사본 HOME 에 없는 완료 상태는 /// 퀘스트 화면(C 그룹)의 「보상 완료」와 맞춘다. String get _claimLabel => appL10n.questClaim; String get _startLabel => appL10n.questStart; String get _doneLabel => appL10n.questRewardDone; /// 홈 「TODAY'S QUEST」 가로 카드 — 사본 `Frame 1984081083`(196×137 · #1A1A1C · r12 · /// 패딩 12 · 간격 28): 제목 16 · 상태 14 #A3A2A0 · 오른쪽 「+보상」 20 #2AC28B, 아래 37 버튼 /// (보상 가능 = 초록 #3EB58A 흰 글자 · 진행 중 = 흰 바탕 검정 글자). 누르면 퀘스트 목록으로 간다. class QuestCard extends StatelessWidget { const QuestCard({super.key, required this.quest, this.onTap}); final QuestSummary quest; final VoidCallback? onTap; static const width = 196.0; @override Widget build(BuildContext context) { final (label, fill, ink) = switch (quest.state) { QuestState.claimable => ( _claimLabel, const Color(0xFF3EB58A), AppColors.onCosmic, ), QuestState.inProgress => (_startLabel, AppColors.onCosmic, Colors.black), QuestState.done => ( _doneLabel, AppColors.cosmicSurfaceRaised, AppColors.onCosmicMuted, ), }; return Material( color: AppColors.cosmicSurfaceContainer, borderRadius: BorderRadius.circular(AppRadius.md), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(AppRadius.md), child: Padding( padding: const EdgeInsets.all(AppSpace.s3), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( quest.title, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontFamily: AppFonts.body, fontSize: 16, fontWeight: FontWeight.w600, height: 1.4, color: AppColors.onCosmic, ), ), const SizedBox(height: 6), // 타임어택은 받기 전까지 남은 시간을 센다(FR-QST-06). if (quest.endsAt case final endsAt? when quest.state != QuestState.done) TimeAttackCountdown( endsAt: endsAt, builder: (context, left) => _subtitleText( left == null ? context.l10n.questTimeAttackClosed : context.l10n.questTimeAttackLeft( formatLeft(left), ), ), ) else _subtitleText(QuestRow._subtitle(quest)), ], ), ), const SizedBox(width: AppSpace.s2), Text( _reward(quest), style: const TextStyle( fontFamily: AppFonts.body, fontSize: 20, fontWeight: FontWeight.w600, height: 1, color: Color(0xFF2AC28B), ), ), ], ), Container( height: 37, alignment: Alignment.center, decoration: BoxDecoration( color: fill, borderRadius: BorderRadius.circular(AppRadius.sm), ), child: Text( label, style: TextStyle( fontFamily: AppFonts.body, fontSize: 14, fontWeight: FontWeight.w600, height: 1, color: ink, ), ), ), ], ), ), ), ); } } Widget _subtitleText(String text) => Text( text, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontFamily: AppFonts.body, fontSize: 14, fontWeight: FontWeight.w600, height: 1.4, color: AppColors.onCosmicVariant, ), ); /// 운영값을 못 읽었으면 숫자를 지어내지 않는다. String _reward(QuestSummary quest) => quest.rewardPop == null ? 'POP' : '+${formatPop(quest.rewardPop!)}';