214 lines
7.2 KiB
Dart
214 lines
7.2 KiB
Dart
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<MyListsScreen> createState() => _MyListsScreenState();
|
|
}
|
|
|
|
class _MyListsScreenState extends State<MyListsScreen> {
|
|
_MySort _sort = _MySort.latest;
|
|
|
|
/// 표본 — 시안 `370:228289` 문구. 서버가 붙으면 INSIDE 저장소의 내 글로 옮긴다.
|
|
static final _insides = () {
|
|
final now = DateTime.now();
|
|
return <InsideQuestion>[
|
|
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<void>(
|
|
builder: (_) => InsideDetailScreen(question: items[i]),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _campaignList(BuildContext context) =>
|
|
FutureBuilder<List<CampaignCardData>>(
|
|
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<void>(
|
|
builder: (_) => CampaignDetailScreen(
|
|
campaignId: mine[i].id,
|
|
repository: widget.campaigns,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|