131 lines
5.3 KiB
Dart
131 lines
5.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:friendly_map/modules/app/app.dart';
|
|
import 'package:friendly_map/modules/inference/inference.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';
|
|
|
|
class _InferData {
|
|
const _InferData(this.person, this.guesses, this.links);
|
|
final Person? person;
|
|
final List<Guess> guesses;
|
|
final List<Link> links;
|
|
}
|
|
|
|
const _manualRelations = ['동창', '동문', '직장 동료', '전 직장 동료', '같은 모임', '같은 여행', '친척', '이웃', '기타'];
|
|
|
|
/// 어디서 봤지? 주고받은 명함을 견주어 추측하고, 맞으면 연결 고리로 확정한다. 직접 적을 수도 있다.
|
|
class InferScreen extends StatefulWidget {
|
|
const InferScreen({super.key, required this.personId});
|
|
|
|
final String personId;
|
|
|
|
@override
|
|
State<InferScreen> createState() => _InferScreenState();
|
|
}
|
|
|
|
class _InferScreenState extends State<InferScreen> {
|
|
final _detail = TextEditingController();
|
|
String _relation = _manualRelations.first;
|
|
|
|
@override
|
|
void dispose() {
|
|
_detail.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<_InferData> _load(FriendlyApp app) async => _InferData(
|
|
await app.friends.person(widget.personId),
|
|
await app.network.guessesFor(widget.personId),
|
|
await app.network.links(widget.personId),
|
|
);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final app = AppScope.of(context).app;
|
|
return AsyncView<_InferData>(
|
|
load: _load,
|
|
builder: (context, d) {
|
|
final name = d.person?.name ?? '이 사람';
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('어디서 봤지?')),
|
|
body: ListView(padding: const EdgeInsets.only(bottom: 32), children: [
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(20, 4, 20, 12),
|
|
child: Text('$name님과 주고받은 명함을 내 명함과 견주어 봤어요.', style: const TextStyle(color: Pal.muted, height: 1.5)),
|
|
),
|
|
if (d.guesses.isEmpty)
|
|
const EmptyNote('겹치는 곳을 아직 찾지 못했어요.\n명함을 더 주고받거나 아래에서 직접 적어 보세요.'),
|
|
for (final g in d.guesses)
|
|
Panel(
|
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
Row(children: [
|
|
Text(g.relation, style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w700)),
|
|
const SizedBox(width: 8),
|
|
Expanded(child: Text(g.detail, style: const TextStyle(color: Pal.muted), overflow: TextOverflow.ellipsis)),
|
|
Text('${g.confidence}%', style: const TextStyle(color: Pal.accent, fontWeight: FontWeight.w700)),
|
|
]),
|
|
const SizedBox(height: 6),
|
|
Text(g.reason, style: const TextStyle(fontSize: 13, height: 1.4)),
|
|
const SizedBox(height: 8),
|
|
Align(
|
|
alignment: Alignment.centerRight,
|
|
child: FilledButton.tonal(
|
|
onPressed: () async {
|
|
await app.network.addLink(widget.personId, g.relation, g.detail);
|
|
if (context.mounted) showNote(context, '연결 고리로 확정했어요.');
|
|
},
|
|
child: const Text('맞아요'),
|
|
),
|
|
),
|
|
]),
|
|
),
|
|
const SectionHeader('직접 적기'),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: Wrap(spacing: 8, runSpacing: 8, children: [
|
|
for (final r in _manualRelations)
|
|
ChoiceChip(label: Text(r), selected: _relation == r, onSelected: (_) => setState(() => _relation = r)),
|
|
]),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
|
|
child: TextField(controller: _detail, decoration: const InputDecoration(labelText: '어디서? (예: 가람초 27회, 제주 여행)')),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
|
|
child: SizedBox(
|
|
height: 48,
|
|
width: double.infinity,
|
|
child: FilledButton(
|
|
onPressed: () async {
|
|
await app.network.addLink(widget.personId, _relation, _detail.text.trim());
|
|
_detail.clear();
|
|
if (context.mounted) showNote(context, '연결 고리를 남겼어요.');
|
|
},
|
|
child: const Text('연결 고리 남기기'),
|
|
),
|
|
),
|
|
),
|
|
if (d.links.isNotEmpty) const SectionHeader('남겨 둔 연결 고리'),
|
|
for (final l in d.links)
|
|
Panel(
|
|
child: Row(children: [
|
|
Expanded(child: Text(l.detail.isEmpty ? l.relation : '${l.relation} · ${l.detail}')),
|
|
IconButton(
|
|
tooltip: '지우기',
|
|
onPressed: () => app.network.removeLink(l.id),
|
|
icon: const Icon(Icons.close, color: Pal.muted),
|
|
),
|
|
]),
|
|
),
|
|
]),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|