HOME · GALAXY · 행성·성운 상세 · 팬덤 라운지 · 이클립스 화면

This commit is contained in:
Sn-Kinos
2026-09-29 17:06:48 +09:00
parent c4e9a9430e
commit 5de04ac281
44 changed files with 14898 additions and 16 deletions
@@ -0,0 +1,129 @@
import 'package:flutter/material.dart';
import '../../../core/theme/tokens.dart';
import '../home_data.dart';
import '../../../core/i18n/app_locale.dart';
/// HOT ARCHIVE — 진행 중인 캠페인 한 건.
///
/// Figma 「다이브짱 / 92% 달성 / 강남역 초대형 LED 광고 / 48 · 24 · 82」.
/// 정책 설계서 §P-09 — 실패 시 100% 자동 환불이 이 기능의 검수 기준이다.
class CampaignCard extends StatelessWidget {
const CampaignCard({super.key, required this.campaign, this.onTap});
final CampaignHighlight campaign;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
final t = Theme.of(context).textTheme;
return Material(
color: AppColors.cosmicSurfaceContainer,
borderRadius: BorderRadius.circular(AppRadius.lg),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(AppRadius.lg),
child: Padding(
padding: const EdgeInsets.all(AppSpace.s4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 시안 HOT ARCHIVE 카드(252:159447) — 위에 이미지 342×228.
ClipRRect(
borderRadius: BorderRadius.circular(AppRadius.sm),
child: AspectRatio(
aspectRatio: 342 / 228,
child: Image.asset('assets/figma/campaign-sample.png', fit: BoxFit.cover),
),
),
const SizedBox(height: AppSpace.s3),
Row(
children: [
Text(campaign.authorName, style: t.bodySmall),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpace.s2,
vertical: 2,
),
decoration: BoxDecoration(
color: AppColors.cosmicAccent.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(AppRadius.full),
),
child: Text(
context.l10n.campaignAchieved('${campaign.achievedPercent}'),
style: t.labelSmall?.copyWith(
color: AppColors.cosmicAccent,
),
),
),
],
),
const SizedBox(height: AppSpace.s3),
Text(campaign.title, style: t.titleMedium),
const SizedBox(height: AppSpace.s2),
Text(
campaign.description,
style: t.bodySmall,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: AppSpace.s3),
ClipRRect(
borderRadius: BorderRadius.circular(AppRadius.full),
child: LinearProgressIndicator(
value: (campaign.achievedPercent / 100).clamp(0.0, 1.0),
minHeight: 6,
backgroundColor: AppColors.cosmicSurfaceRaised,
valueColor: const AlwaysStoppedAnimation(
AppColors.cosmicAccent,
),
),
),
const SizedBox(height: AppSpace.s3),
Row(
children: [
_Stat(icon: Icons.people_outline, value: campaign.supporters),
const SizedBox(width: AppSpace.s4),
_Stat(
icon: Icons.chat_bubble_outline,
value: campaign.comments,
),
const SizedBox(width: AppSpace.s4),
_Stat(icon: Icons.favorite_border, value: campaign.likes),
const Spacer(),
Text(
context.l10n.campaignView,
style: t.bodySmall?.copyWith(color: AppColors.cosmicAccent),
),
const Icon(
Icons.chevron_right,
size: 16,
color: AppColors.cosmicAccent,
),
],
),
],
),
),
),
);
}
}
class _Stat extends StatelessWidget {
const _Stat({required this.icon, required this.value});
final IconData icon;
final int value;
@override
Widget build(BuildContext context) => Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 14, color: AppColors.onCosmicVariant),
const SizedBox(width: AppSpace.s1),
Text('$value', style: Theme.of(context).textTheme.labelSmall),
],
);
}