97 lines
4.1 KiB
Dart
97 lines
4.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:friendly_map/modules/app/app.dart';
|
|
import 'package:friendly_map/modules/platform/platform.dart';
|
|
import 'package:friendly_map/modules/storage/storage.dart';
|
|
import 'package:friendly_map/modules/visibility/visibility.dart';
|
|
|
|
import '../app_scope.dart';
|
|
import '../theme.dart';
|
|
import '../widgets/async_view.dart';
|
|
import '../widgets/common.dart';
|
|
import '../widgets/radar_view.dart';
|
|
|
|
class _LocData {
|
|
const _LocData(this.on, this.mine, this.friends, this.recipients);
|
|
final bool on;
|
|
final GeoPoint? mine;
|
|
final List<FriendLocation> friends;
|
|
final Map<Person, VLevel> recipients;
|
|
}
|
|
|
|
/// 위치: 켜면 공개 범위 표에서 허용한 친구에게만 내 위치를 보내고, 친구들이 보내 준 위치를 레이더로 본다.
|
|
class LocationScreen extends StatefulWidget {
|
|
const LocationScreen({super.key});
|
|
|
|
@override
|
|
State<LocationScreen> createState() => _LocationScreenState();
|
|
}
|
|
|
|
class _LocationScreenState extends State<LocationScreen> {
|
|
bool _busy = false;
|
|
|
|
Future<_LocData> _load(FriendlyApp app) async =>
|
|
_LocData(await app.location.sharingOn(), await app.location.mine(), await app.location.friends(), await app.location.recipients());
|
|
|
|
Future<void> _share() async {
|
|
final app = AppScope.of(context).app;
|
|
setState(() => _busy = true);
|
|
final n = await app.location.shareNow();
|
|
if (!mounted) return;
|
|
setState(() => _busy = false);
|
|
showNote(context, n == 0 ? '보낼 수 있는 친구가 없거나 위치를 얻지 못했어요.' : '친구 $n명에게 위치를 보냈어요.');
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final app = AppScope.of(context).app;
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('위치')),
|
|
body: AsyncView<_LocData>(
|
|
load: _load,
|
|
builder: (context, d) {
|
|
final dots = [
|
|
if (d.mine != null)
|
|
for (final f in d.friends)
|
|
RadarDot(f.person.name, bearingDegrees(d.mine!, f.point), distanceMeters(d.mine!, f.point)),
|
|
];
|
|
return ListView(padding: const EdgeInsets.only(bottom: 32), children: [
|
|
Panel(
|
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
ToggleRow(
|
|
title: '내 위치 공유',
|
|
subtitle: app.location.available ? '꺼져 있으면 아무에게도 보내지 않아요' : '이 기기에서는 위치를 쓸 수 없어요',
|
|
value: d.on,
|
|
onChanged: (v) => app.location.setSharing(app.location.available && v),
|
|
),
|
|
if (d.on) ...[
|
|
Text(
|
|
d.recipients.isEmpty
|
|
? '받을 수 있는 친구가 없어요. 내 정보의 공개 범위 표에서 "실시간 위치"를 열어 주세요.'
|
|
: '받는 친구: ${d.recipients.entries.map((e) => '${e.key.name}${e.value == VLevel.partial ? '(대략)' : ''}').join(', ')}',
|
|
style: const TextStyle(fontSize: 12, color: Pal.muted, height: 1.5),
|
|
),
|
|
const SizedBox(height: 8),
|
|
FilledButton.tonal(onPressed: _busy ? null : _share, child: const Text('지금 보내기')),
|
|
],
|
|
]),
|
|
),
|
|
const SectionHeader('친구 위치'),
|
|
if (d.mine == null) const EmptyNote('내 위치를 얻지 못했어요. 위치 권한과 GPS 를 확인해 주세요.'),
|
|
Center(child: RadarView(dots: dots)),
|
|
if (d.friends.isEmpty) const EmptyNote('친구가 보내 준 위치가 아직 없어요.'),
|
|
for (final f in d.friends)
|
|
ListTile(
|
|
leading: Avatar(f.person.name, size: 40),
|
|
title: Text(f.person.name),
|
|
subtitle: Text(
|
|
d.mine == null ? '${f.point.lat.toStringAsFixed(3)}, ${f.point.lng.toStringAsFixed(3)}' : '${distanceLabel(distanceMeters(d.mine!, f.point))} · ${shortTime(f.at)}',
|
|
style: const TextStyle(fontSize: 12, color: Pal.muted),
|
|
),
|
|
),
|
|
]);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|