import 'package:flutter/material.dart'; import '../../core/i18n/app_locale.dart'; import '../../core/theme/tokens.dart'; import 'campaign_data.dart'; import 'widgets/campaign_rank.dart'; import 'widgets/campaign_style.dart'; /// 기여 랭킹 더보기 — 화면 대조 원장 F5 · Figma `333:70971`. /// /// 명세 `536:14361` — *내 순위 · 순위 변동값 · 프로필 · 닉네임 · 기여 POP* /// 명세 `536:14371` — *기여 POP에 따른 랭킹 소팅 · 실시간 데이터 기준* /// 명세 `536:14382` — *랭킹 필터 바텀시트 — 실시간 · 일간 · 주간* class CampaignRankingScreen extends StatefulWidget { CampaignRankingScreen({ super.key, required this.campaignId, CampaignRepository? repository, }) : repository = repository ?? SampleCampaignRepository(); final String campaignId; final CampaignRepository repository; @override State createState() => _CampaignRankingScreenState(); } class _CampaignRankingScreenState extends State { List _ranks = const []; RankWindow _window = RankWindow.live; bool _loading = true; @override void initState() { super.initState(); _load(); } Future _load() async { setState(() => _loading = true); final ranks = await widget.repository.ranking( widget.campaignId, window: _window, ); if (!mounted) return; setState(() { _ranks = ranks; _loading = false; }); } ContributorRank? get _me { for (final r in _ranks) { if (r.isMe) return r; } return null; } @override Widget build(BuildContext context) { final me = _me; final sorted = [..._ranks]..sort((a, b) => a.rank.compareTo(b.rank)); final top = sorted.take(3).toList(); final rest = sorted.skip(3).toList(); // 시안(333:70971) — 「USER RANKING」 · 나의 기여 랭킹 카드 · 「실시간 랭킹 ⌄」 필터 · 단상 · 목록. return Scaffold( appBar: AppBar(title: const Text('USER RANKING')), body: _loading ? const Center(child: CircularProgressIndicator()) : ListView( padding: const EdgeInsets.all(AppSpace.s4), children: [ // 내 순위를 맨 위에 고정한다. 목록을 훑지 않아도 보여야 한다. if (me != null) ...[ Text( context.l10n.campaignMyRanking, style: campaignText(24, 500, AppColors.onCosmic, height: 1), ), const SizedBox(height: AppSpace.s4), Container( decoration: BoxDecoration( color: const Color(0xFF19191C), borderRadius: BorderRadius.circular(AppRadius.sm), ), child: RankRow( rank: me, padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), ), const SizedBox(height: AppSpace.s5), ], CampaignRankHeader(window: _window, onTap: _pickWindow), const SizedBox(height: AppSpace.s3), RankPodium(top: top), const SizedBox(height: AppSpace.s3), for (final r in rest) RankRow(rank: r), ], ), ); } Future _pickWindow() async { final picked = await showModalBottomSheet( context: context, builder: (context) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ for (final w in RankWindow.values) ListTile( title: Text(w.label), trailing: _window == w ? const Icon(Icons.check) : null, onTap: () => Navigator.of(context).pop(w), ), ListTile( title: Text(context.l10n.commonCancel), onTap: () => Navigator.of(context).pop(), ), ], ), ), ); if (picked == null || !mounted) return; setState(() => _window = picked); await _load(); } } /// 「실시간 랭킹 ⌄」 — 18/600 · 아래 꺾쇠 20. 누르면 구간 바텀시트(명세 `536:14382`). class CampaignRankHeader extends StatelessWidget { const CampaignRankHeader({ super.key, required this.window, required this.onTap, }); final RankWindow window; final VoidCallback onTap; @override Widget build(BuildContext context) => Align( alignment: Alignment.centerLeft, child: InkWell( onTap: onTap, child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( context.l10n.campaignWindowRanking(window.label), style: campaignText(18, 600, AppColors.onCosmic, spacing: 0), ), const SizedBox(width: 4), const ImageIcon( AssetImage('assets/figma/campaign-chevron-down.png'), size: 20, color: AppColors.onCosmic, ), ], ), ), ); }