187 lines
6.6 KiB
Dart
187 lines
6.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import 'planet_data.dart';
|
|
import '../../core/i18n/app_locale.dart';
|
|
import '../../core/ui/bottom_inset.dart';
|
|
|
|
class PlanetHistoryScreen extends StatefulWidget {
|
|
const PlanetHistoryScreen({
|
|
super.key,
|
|
required this.repository,
|
|
required this.fandomName,
|
|
});
|
|
final PlanetRepository repository;
|
|
final String fandomName;
|
|
@override
|
|
State<PlanetHistoryScreen> createState() => _PlanetHistoryScreenState();
|
|
}
|
|
|
|
class _PlanetHistoryScreenState extends State<PlanetHistoryScreen> {
|
|
final _items = <PlanetHistoryEntry>[];
|
|
String? _cursor;
|
|
bool _loading = false;
|
|
bool _loaded = false;
|
|
bool _failed = false;
|
|
bool _personal = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_load();
|
|
}
|
|
|
|
Future<void> _load() async {
|
|
if (_loading) return;
|
|
setState(() {
|
|
_loading = true;
|
|
_failed = false;
|
|
});
|
|
try {
|
|
final page = await widget.repository.history(
|
|
cursor: _cursor,
|
|
personal: _personal,
|
|
);
|
|
if (!mounted) return;
|
|
setState(() {
|
|
final ids = _items.map((item) => item.id).toSet();
|
|
_items.addAll(page.items.where((item) => ids.add(item.id)));
|
|
_cursor = page.nextCursor;
|
|
_loaded = true;
|
|
});
|
|
} catch (_) {
|
|
if (mounted) setState(() => _failed = true);
|
|
} finally {
|
|
if (mounted) setState(() => _loading = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) => Scaffold(
|
|
appBar: AppBar(title: Text(context.l10n.historyTitle)),
|
|
body: ListView(
|
|
padding: withBottomInset(context, const EdgeInsets.all(16)),
|
|
children: [
|
|
Text(widget.fandomName, style: Theme.of(context).textTheme.titleLarge),
|
|
const SizedBox(height: 16),
|
|
SegmentedButton<bool>(
|
|
segments: [
|
|
ButtonSegment(
|
|
value: false,
|
|
label: Text(context.l10n.homeFandomHistory),
|
|
),
|
|
ButtonSegment(value: true, label: Text(context.l10n.historyMine)),
|
|
],
|
|
selected: {_personal},
|
|
onSelectionChanged: _loading
|
|
? null
|
|
: (selection) {
|
|
if (selection.single == _personal) return;
|
|
setState(() {
|
|
_personal = selection.single;
|
|
_items.clear();
|
|
_cursor = null;
|
|
_loaded = false;
|
|
});
|
|
_load();
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
for (final item in _items)
|
|
_Stamp(
|
|
historic: item.kind.isHistoric,
|
|
child: ListTile(
|
|
contentPadding: EdgeInsets.zero,
|
|
leading: Icon(switch (item.kind) {
|
|
PlanetHistoryKind.evolution => Icons.auto_awesome,
|
|
PlanetHistoryKind.hibernation => Icons.bedtime_outlined,
|
|
PlanetHistoryKind.wakeUp => Icons.wb_sunny_outlined,
|
|
PlanetHistoryKind.zap => Icons.bolt,
|
|
PlanetHistoryKind.questReward => Icons.task_alt,
|
|
PlanetHistoryKind.companyAdBonus => Icons.play_circle_outline,
|
|
PlanetHistoryKind.funding => Icons.favorite_border,
|
|
PlanetHistoryKind.eclipseSuccess => Icons.shield_outlined,
|
|
PlanetHistoryKind.emblemEvolution =>
|
|
Icons.military_tech_outlined,
|
|
PlanetHistoryKind.founderBadge =>
|
|
Icons.workspace_premium_outlined,
|
|
}),
|
|
title: Text(switch (item.kind) {
|
|
PlanetHistoryKind.evolution => context.l10n.historyEvolution(
|
|
'${item.fromStage}',
|
|
'${item.toStage}',
|
|
),
|
|
PlanetHistoryKind.hibernation =>
|
|
context.l10n.historyHibernation,
|
|
PlanetHistoryKind.wakeUp => context.l10n.historyWakeUp,
|
|
PlanetHistoryKind.zap => context.l10n.historyZap,
|
|
PlanetHistoryKind.questReward => context.l10n.historyQuest,
|
|
PlanetHistoryKind.companyAdBonus => context.l10n.historyAdBonus,
|
|
PlanetHistoryKind.funding => context.l10n.historyFunding,
|
|
PlanetHistoryKind.eclipseSuccess =>
|
|
context.l10n.historyEclipseSuccess,
|
|
PlanetHistoryKind.emblemEvolution =>
|
|
'${context.l10n.historyEmblemEvolution} · STAGE ${item.toStage ?? ''}',
|
|
PlanetHistoryKind.founderBadge =>
|
|
'${context.l10n.historyFounderBadge} · #${(item.memberNo ?? 0).toString().padLeft(3, '0')}',
|
|
}),
|
|
subtitle: Text(
|
|
[
|
|
MaterialLocalizations.of(context)
|
|
.formatMediumDate(item.occurredAt.toLocal()),
|
|
if (item.amount != null)
|
|
context.l10n.historyGrowthPop('${item.amount}'),
|
|
if (item.participationConfirmed) context.l10n.historyJoined,
|
|
].join('\n'),
|
|
),
|
|
),
|
|
),
|
|
if (_loading) const Center(child: CircularProgressIndicator()),
|
|
if (_failed) ...[
|
|
Text(context.l10n.historyLoadFailed),
|
|
TextButton(onPressed: _load, child: Text(context.l10n.commonRetry)),
|
|
] else if (_loaded && _items.isEmpty && _cursor == null)
|
|
Text(
|
|
_personal
|
|
? context.l10n.historyMineEmpty
|
|
: context.l10n.historyFandomEmpty,
|
|
),
|
|
if (!_loading && !_failed && _cursor != null)
|
|
TextButton(onPressed: _load, child: Text(context.l10n.historyMore)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 「역사적 사건」(UX·FX v1.5 §7.2)은 테두리 Stamp 와 표지로 일반 활동 줄과 가른다.
|
|
/// 앱 Figma 에 PASSPORT 기록 화면이 없어 화면 테마 색만 쓴다.
|
|
class _Stamp extends StatelessWidget {
|
|
const _Stamp({required this.historic, required this.child});
|
|
final bool historic;
|
|
final Widget child;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (!historic) return child;
|
|
final scheme = Theme.of(context).colorScheme;
|
|
return Container(
|
|
margin: const EdgeInsets.symmetric(vertical: 4),
|
|
padding: const EdgeInsets.fromLTRB(12, 8, 12, 0),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: scheme.primary),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
context.l10n.historyHistoric,
|
|
style: Theme.of(context).textTheme.labelSmall
|
|
?.copyWith(color: scheme.primary),
|
|
),
|
|
child,
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|