INSIDE · STATION · 참여형 번역 · 알림 · MY 화면
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user