import 'package:flutter/material.dart'; import '../campaign/campaign_data.dart'; import '../campaign/campaign_detail_screen.dart'; import '../campaign/widgets/campaign_list_card.dart'; import '../inside/inside_data.dart'; import '../inside/inside_detail_screen.dart'; import '../inside/inside_screen.dart' show InsideQuestionCard; import 'widgets/my_cards.dart'; import '../../core/i18n/app_locale.dart'; /// 내 인사이드 · 내 캠페인 — 화면 대조 원장 F6. /// /// Figma `370:228289`(내 인사이드) · `370:229665`(내 캠페인). /// 명세 `554:22382`(게시글 리스트 영역) 계열이며 **머리글과 빈 상태만 다르고** /// 구조가 같아 한 화면에 종류를 받아 쓴다. enum MyListKind { inside, campaign; String get title => switch (this) { inside => appL10n.myListInside, campaign => appL10n.myListCampaign, }; String get emptyMessage => switch (this) { inside => appL10n.myListInsideEmpty, campaign => appL10n.myListCampaignEmpty, }; } /// 정렬 칩 — 시안 `370:228289` 「최신순 · POP순 · 채택 완료」 · `370:229665` 「최신순 · 달성 완료」. enum _MySort { latest, pop, solved, achieved } class MyListsScreen extends StatefulWidget { const MyListsScreen({super.key, required this.kind, this.campaigns}); final MyListKind kind; /// 내 캠페인의 원천. null 이면 표본(목 모드). 저장소 선택은 repositories.dart. final CampaignRepository? campaigns; @override State createState() => _MyListsScreenState(); } class _MyListsScreenState extends State { _MySort _sort = _MySort.latest; /// 표본 — 시안 `370:228289` 문구. 서버가 붙으면 INSIDE 저장소의 내 글로 옮긴다. static final _insides = () { final now = DateTime.now(); return [ InsideQuestion( id: 'i1', title: 'BTS 컴백 알려주세요!', body: 'BTS 한국 컴백 시기가 궁급합니다!\n아시는 분들 댓글 부탁드려요 ㅠㅠ', authorName: 'BTS_Hong', createdAt: now, bountyPop: 100, isMine: true, ), InsideQuestion( id: 'i2', title: '화양연화 세계관 해석해주실 분?', body: '이번 뮤비랑 연결되는 부분이 헷갈리네요. 타임라인 정리해주실 천재 아미 구합니다!', authorName: 'BTS_Hong', createdAt: now.subtract(const Duration(minutes: 10)), bountyPop: 500, viewCount: 3, isMine: true, ), InsideQuestion( id: 'i3', title: 'BTS 이 포즈 뭔가요?', body: '아미 분들 이 포즈 뭔지 알려주실 수 있나요?', authorName: 'BTS_Hong', createdAt: now.subtract(const Duration(days: 30)), bountyPop: 800, answerCount: 24, viewCount: 82, isMine: true, isSolved: true, ), ]; }(); List<_MySort> get _sorts => switch (widget.kind) { MyListKind.inside => const [_MySort.latest, _MySort.pop, _MySort.solved], MyListKind.campaign => const [_MySort.latest, _MySort.achieved], }; @override Widget build(BuildContext context) => Scaffold( appBar: AppBar( title: Text(widget.kind.title), bottom: PreferredSize( preferredSize: const Size.fromHeight(36), child: Align( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.fromLTRB(16, 2, 16, 2), child: Row( children: [ for (final (i, sort) in _sorts.indexed) ...[ if (i > 0) const SizedBox(width: 8), MySortChip( label: switch (sort) { _MySort.latest => context.l10n.sortLatest, _MySort.pop => context.l10n.sortPop, _MySort.solved => context.l10n.sortSolved, _MySort.achieved => context.l10n.sortAchieved, }, icon: switch (sort) { _MySort.latest => MySortChip.clock, _MySort.pop => MySortChip.bolt, _ => MySortChip.fileCheck, }, selected: _sort == sort, onTap: () => setState(() => _sort = sort), ), ], ], ), ), ), ), ), body: switch (widget.kind) { MyListKind.inside => _insideList(context), MyListKind.campaign => _campaignList(context), }, ); Widget _insideList(BuildContext context) { final items = switch (_sort) { _MySort.pop => [ ..._insides, ]..sort((a, b) => b.bountyPop.compareTo(a.bountyPop)), _MySort.solved => _insides.where((q) => q.isSolved).toList(), _ => _insides, }; if (items.isEmpty) { return Center( child: Text( widget.kind.emptyMessage, style: Theme.of(context).textTheme.bodyMedium, ), ); } // 카드는 INSIDE 목록과 같은 것을 쓴다(시안도 같은 컴포넌트다). return ListView.separated( padding: const EdgeInsets.all(16), itemCount: items.length, separatorBuilder: (_, _) => const Padding( padding: EdgeInsets.symmetric(vertical: 2), child: SizedBox(height: 1, child: ColoredBox(color: Color(0xFF1A1A1A))), ), itemBuilder: (context, i) => InsideQuestionCard( question: items[i], onTap: () => Navigator.of(context, rootNavigator: true).push( MaterialPageRoute( builder: (_) => InsideDetailScreen(question: items[i]), ), ), ), ); } Widget _campaignList(BuildContext context) => FutureBuilder>( future: (widget.campaigns ?? SampleCampaignRepository()).mine(), builder: (context, snap) { if (snap.connectionState != ConnectionState.done) { return const Center(child: CircularProgressIndicator()); } // 내가 만든 것만 남긴다. 「달성 완료」는 목표를 채운 것만. final mine = (snap.data ?? const []) .where((c) => c.mine) .where( (c) => _sort != _MySort.achieved || c.status == CampaignStatus.success, ) .toList(); if (mine.isEmpty) { return Center( child: Text( widget.kind.emptyMessage, style: Theme.of(context).textTheme.bodyMedium, ), ); } return ListView.separated( padding: const EdgeInsets.all(16), itemCount: mine.length, separatorBuilder: (_, _) => const SizedBox(height: 16), itemBuilder: (context, i) => CampaignListCard( card: mine[i], mine: true, onOpen: () => Navigator.of(context, rootNavigator: true).push( MaterialPageRoute( builder: (_) => CampaignDetailScreen( campaignId: mine[i].id, repository: widget.campaigns, ), ), ), ), ); }, ); }