import 'dart:async'; import 'package:friendly_map/modules/app/app.dart'; import 'package:friendly_map/modules/discovery/discovery.dart'; import 'package:friendly_map/modules/keys/keys.dart'; import 'package:friendly_map/modules/platform_android/platform_android.dart'; import 'package:friendly_map/modules/transport/transport.dart'; import 'package:friendly_map/modules/ui/ui.dart'; /// 같은 네트워크 직접 연결이 쓰는 포트. const int lanPort = 48766; /// 보관함 서버 주소를 적어 두는 설정 이름. 비어 있으면 같은 네트워크 직접 연결만 쓴다. const String mailboxUrlKey = 'mailbox_url'; /// 안드로이드용 조립: 어떤 부품(보안 저장소, 암호화 DB, 직접 연결, 보관함)을 쓸지 여기서만 정한다. /// 다른 모듈은 서로의 구현을 모른다. 부품을 바꾸려면 이 파일의 해당 줄만 바꾸면 된다. class AndroidBootstrap implements Bootstrap { AndroidBootstrap() : _keys = LocalKeyProvider(AndroidSecureKeyStore()); final LocalKeyProvider _keys; Timer? _refresh; Timer? _location; Timer? _events; @override Future hasKeys() => _keys.isSetUp(); @override Future createKeys() => _keys.setUpNew(); @override Future restoreKeys(String text) async { final tokens = text.split(RegExp(r'\s+')).where(RecoveryShares.looksLikeShare).toList(); if (tokens.isEmpty) return _keys.restore(text); final phrase = await RecoveryShares.combine(tokens); return phrase != null && await _keys.restore(phrase); } @override Future> splitRecovery(int n, int k) async { final phrase = await _keys.showPhrase(); if (phrase == null) throw StateError('복구 코드가 없습니다'); return RecoveryShares.split(phrase, n: n, k: k); } @override Future recoveryPhrase() => _keys.showPhrase(); @override Future openApp() async { final storage = await openEncryptedStorage(_keys); final identityKeys = IdentityKeys(_keys.derive); final lan = LanDirectory(AndroidLanDiscovery()); // 내 전화번호는 내 정보를 정한 뒤에야 알 수 있어서, 연결은 앱이 시작될 때 만든다. final transport = LateTransport(() async { final me = await storage.settings.loadProfile(); final url = (await storage.settings.getText(mailboxUrlKey))?.trim() ?? ''; return CompositeTransport([ DirectTransport( port: lanPort, resolve: (phone) async { final p = lan.find(phone); return p == null ? null : DirectAddress(p.host, p.port); }, ), if (me != null && url.isNotEmpty) MailboxTransport(baseUrl: url, myPhone: me.phone, token: await _keys.mailboxToken()), ]); }); final app = FriendlyApp(AppDeps( storage: storage, transport: transport, timeSource: transport, cipher: SharedPayloadCipher(identityKeys), identity: LocalIdentityProvider(identityKeys), probe: AndroidStorageProbe(), lan: lan, location: AndroidLocationProvider(), callLog: AndroidCallLog(), phone: AndroidPhoneActions(), notifier: AndroidNotifier(), contacts: AndroidContacts(), instant: AndroidInstantNotifier(), verifier: LatePhoneVerifier(() async { final me = await storage.settings.loadProfile(); final url = (await storage.settings.getText(mailboxUrlKey))?.trim() ?? ''; if (me == null || url.isEmpty) return null; return MailboxVerifier(baseUrl: url, myPhone: me.phone, token: await _keys.mailboxToken()); }), )) ..lanPort = lanPort; // 보관함에 쌓인 봉투를 주기적으로 가져온다. _refresh?.cancel(); _refresh = Timer.periodic(const Duration(seconds: 30), (_) => app.refresh().catchError((Object _) {})); _location?.cancel(); _location = Timer.periodic(const Duration(minutes: 1), (_) => app.location.shareNow().catchError((Object _) => 0)); _events?.cancel(); _events = Timer.periodic(const Duration(hours: 6), (_) => app.events.reschedule().catchError((Object _) => 0)); return app; } }