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 '../translation/translation_data.dart' show TranslationRepository; 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, this.insides, this.translation, }); final MyListKind kind; /// 내 인사이드의 원천. null 이면 표본(목 모드). 저장소 선택은 repositories.dart. final InsideRepository? insides; /// 내 질문 상세의 번역 원천. null 이면 번역 기능을 끈다. final TranslationRepository? translation; /// 내 캠페인의 원천. null 이면 표본(목 모드). 저장소 선택은 repositories.dart. final CampaignRepository? campaigns; @override State createState() => _MyListsScreenState(); } class _MyListsScreenState extends State { _MySort _sort = _MySort.latest; 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), }, ); /// 내가 쓴 질문 — 화면을 열 때 한 번 읽고, 정렬 칩은 그 목록을 다시 거른다. late final Future> _mine = (widget.insides ?? const SampleInsideRepository()).mine(); Widget _insideList(BuildContext context) => FutureBuilder>( future: _mine, builder: (context, snap) { if (snap.connectionState != ConnectionState.done) { return const Center(child: CircularProgressIndicator()); } return _insideItems(context, snap.data ?? const []); }, ); Widget _insideItems(BuildContext context, List mine) { final items = switch (_sort) { _MySort.pop => [ ...mine, ]..sort((a, b) => b.bountyPop.compareTo(a.bountyPop)), _MySort.solved => mine.where((q) => q.isSolved).toList(), _ => mine, }; 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], repository: widget.insides, translation: widget.translation, ), ), ), ), ); } 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, ), ), ), ), ); }, ); }