frindlymap/lib/modules/ui/src/screens/card_wallet_screen.dart

175 lines
6.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:friendly_map/modules/app/app.dart';
import 'package:friendly_map/modules/core/core.dart';
import 'package:friendly_map/modules/storage/storage.dart';
import '../app_scope.dart';
import '../theme.dart';
import '../widgets/async_view.dart';
import '../widgets/common.dart';
import '../widgets/labels.dart';
import 'card_edit_screen.dart';
import 'share_screen.dart';
import 'template_gallery_screen.dart';
class _WalletData {
const _WalletData(this.cards, this.shares, this.resend, this.people, this.labels);
final List<MyCard> cards;
final Map<String, List<ShareRecord>> shares;
final List<ShareRecord> resend;
final Map<String, Person> people;
final Map<String, String> labels;
}
/// 내 명함첩: 용도별 여러 장. 길게 눌러 끌면 순서가 바뀌고, 고친 명함은 "다시 보내기"로 상대에게 보낸다.
class CardWalletScreen extends StatelessWidget {
const CardWalletScreen({super.key});
Future<_WalletData> _load(FriendlyApp app) async {
final cards = await app.cards.mine();
return _WalletData(
cards,
{for (final c in cards) c.id: await app.cards.sharesOf(c.id)},
await app.cards.needingResend(),
{for (final p in await app.friends.people()) p.id: p},
await fieldLabels(app),
);
}
@override
Widget build(BuildContext context) {
final app = AppScope.of(context).app;
return Scaffold(
appBar: AppBar(
title: const Text('내 명함', style: TextStyle(fontWeight: FontWeight.w700)),
actions: [
IconButton(tooltip: '명함 공유', onPressed: () => pushPage(context, const ShareScreen()), icon: const Icon(Icons.ios_share)),
],
),
floatingActionButton: FloatingActionButton.extended(
heroTag: null,
onPressed: () => pushPage(context, const TemplateGalleryScreen()),
icon: const Icon(Icons.add),
label: const Text('명함 추가'),
),
body: AsyncView<_WalletData>(
load: _load,
builder: (context, d) {
return ListView(padding: const EdgeInsets.only(bottom: 100), children: [
if (d.resend.isNotEmpty) _ResendBanner(d),
if (d.cards.isEmpty) const EmptyNote('명함이 없어요. 아래 버튼으로 만들어 보세요.'),
Padding(
padding: const EdgeInsets.fromLTRB(20, 4, 20, 8),
child: Text('길게 눌러 끌면 순서를 바꿀 수 있어요', style: TextStyle(fontSize: 11, color: Pal.muted.withValues(alpha: 0.8))),
),
ReorderableListView(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
buildDefaultDragHandles: true,
onReorderItem: (from, to) => app.cards.move(d.cards[from].id, to),
children: [
for (final c in d.cards)
_CardTile(key: ValueKey(c.id), card: c, shares: d.shares[c.id] ?? const [], people: d.people, labels: d.labels),
],
),
]);
},
),
);
}
}
class _ResendBanner extends StatelessWidget {
const _ResendBanner(this.d);
final _WalletData d;
@override
Widget build(BuildContext context) {
final app = AppScope.of(context).app;
return Panel(
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
const Text('고친 명함을 아직 보내지 않았어요', style: TextStyle(fontWeight: FontWeight.w700)),
const SizedBox(height: 4),
const Text('상대에게는 "다시 보내기"를 눌러야 새 내용이 가요.', style: TextStyle(fontSize: 12, color: Pal.muted)),
const SizedBox(height: 8),
for (final s in d.resend)
Row(children: [
Expanded(child: Text('${d.people[s.peerId]?.name ?? '?'} · ${_title(s)}', overflow: TextOverflow.ellipsis)),
TextButton(
onPressed: () async {
final ok = await app.cards.resend(s.id);
if (context.mounted) showNote(context, ok ? '다시 보냈어요.' : '보내지 못했어요.');
},
child: const Text('다시 보내기'),
),
]),
]),
);
}
String _title(ShareRecord s) {
for (final c in d.cards) {
if (c.id == s.targetId) return c.title;
}
return '명함';
}
}
class _CardTile extends StatelessWidget {
const _CardTile({super.key, required this.card, required this.shares, required this.people, required this.labels});
final MyCard card;
final List<ShareRecord> shares;
final Map<String, Person> people;
final Map<String, String> labels;
@override
Widget build(BuildContext context) {
final app = AppScope.of(context).app;
final lines = filledFields(decodeFields(card.fieldsJson), labels).take(3).toList();
final names = [for (final s in shares) people[s.peerId]?.name ?? '?'];
return Panel(
onTap: () => pushPage(context, CardEditScreen(cardId: card.id)),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
KindChip(card.kind, CardKinds.label(card.kind)),
const SizedBox(width: 8),
Expanded(child: Text(card.title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700), overflow: TextOverflow.ellipsis)),
PopupMenuButton<String>(
tooltip: '더 보기',
onSelected: (v) async {
if (v == 'edit') pushPage(context, CardEditScreen(cardId: card.id));
if (v == 'share') pushPage(context, ShareScreen(initialCardId: card.id));
if (v == 'delete') {
final ok = await askConfirm(context, '명함을 지울까요?', '이미 상대에게 간 사본은 상대 기기에 남아요. 이후 공유는 끝나요.', ok: '지우기');
if (ok) await app.cards.delete(card.id);
}
},
itemBuilder: (_) => const [
PopupMenuItem(value: 'edit', child: Text('고치기')),
PopupMenuItem(value: 'share', child: Text('공유')),
PopupMenuItem(value: 'delete', child: Text('지우기')),
],
),
]),
if (lines.isEmpty) const Text('내용을 채워 보세요', style: TextStyle(color: Pal.muted, fontSize: 13)),
for (final e in lines) Text('${e.key} ${e.value}', style: const TextStyle(fontSize: 13, height: 1.5)),
const SizedBox(height: 6),
Row(children: [
const Icon(Icons.share_outlined, size: 14, color: Pal.muted),
const SizedBox(width: 6),
Expanded(
child: Text(
names.isEmpty ? '공유 안 함' : '${names.join(', ')} 에게 공유 중',
style: const TextStyle(fontSize: 12, color: Pal.muted),
overflow: TextOverflow.ellipsis,
),
),
if (card.shareDefault) const KindChip('family', '새 친구에게 기본 공유'),
]),
]),
);
}
}