mirror of
https://github.com/kuhyx/wake-alarm.git
synced 2026-11-11 19:54:04 +01:00
Dart side of the alarm (items 1, 4, 3). main.dart had no init seam at all; it now has ensureInitialized(), a navigatorKey and a /ring route. - challenge.dart: all five kinds ported from _challenges.py so both halves behave identically. kDismissCodeLength/kDismissFlashSeconds are duplicated from _constants.py, not synced — noted in the file, since Dart cannot read Python. - alarm_scheduler.dart: the Dart side of the single MethodChannel. - ring_screen.dart: room proof first, then a challenge, then dismiss. Back is a no-op (PopScope) so the alarm cannot be escaped. - room_proof.dart: NFC or QR satisfies on its own; steps NEVER do alone — the phone is stationary while you walk to it, so steps only accrue after pickup and can be faked by shaking it in bed. Steps count only together with elapsed time, as the fallback when both others fail. - home_screen: arms the app's own alarm instead of asking the user to confirm in the Clock app (item 1's "no user interaction"). SET_ALARM stays as redundancy with SKIP_UI=true. Re-arm has three hooks and needs all three: startup, BOOT_COMPLETED, and dismiss. The plan assumed LogStore.changes would cover this; that stream does not exist in crdt_sync_dart (verified — the library has no Stream at all), so the picker's save is only one hook and a restart would otherwise arm nothing. Fixed a real gap the API 36 AVD exposed: POST_NOTIFICATIONS was never requested at runtime, so a fresh install sat at importance=NONE and the full-screen intent was rejected outright — the alarm would ring with no dismiss screen. MainActivity now requests it, and canUseFullScreenIntent() also checks areNotificationsEnabled() so the check gates rather than warns. Verified on the API 36 AVD: screen Asleep -> Awake, AlarmActivity became the top resumed activity over the lockscreen, service isForeground=true types=0x2 (mediaPlayback) category=alarm, no crash. flutter test: 54 pass. dart analyze: clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EckWmyfetYq3Mi8MAnUudh
48 lines
1.6 KiB
Dart
48 lines
1.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import 'package:wake_alarm_sync/screens/home_screen.dart';
|
|
import 'package:wake_alarm_sync/screens/ring_screen.dart';
|
|
import 'package:wake_alarm_sync/services/alarm_scheduler.dart';
|
|
import 'package:wake_alarm_sync/ui/theme.dart';
|
|
|
|
/// Route the Kotlin `AlarmActivity` hands to the engine as its initial
|
|
/// route.
|
|
const String kRingRoute = '/ring';
|
|
|
|
/// Navigator key, so the platform side can drive navigation without a context.
|
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
|
|
|
/// Entry point.
|
|
///
|
|
/// `ensureInitialized` is required before any MethodChannel call: the
|
|
/// startup re-arm below runs before the first frame.
|
|
Future<void> main() async {
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
// The log store has no change stream, so the picker's save is the only other
|
|
// arm hook. Without this, restarting the app without touching the picker
|
|
// would leave nothing armed at all.
|
|
await AlarmScheduler().rearm();
|
|
runApp(const WakeAlarmSyncApp());
|
|
}
|
|
|
|
/// Root application widget.
|
|
class WakeAlarmSyncApp extends StatelessWidget {
|
|
/// Creates a [WakeAlarmSyncApp].
|
|
const WakeAlarmSyncApp({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
title: 'Wake Alarm Sync',
|
|
theme: buildAppTheme(),
|
|
navigatorKey: navigatorKey,
|
|
// AlarmActivity sets its initial route to kRingRoute, so the ring screen
|
|
// is what comes up over the lockscreen; the launcher still gets home.
|
|
routes: <String, WidgetBuilder>{
|
|
'/': (_) => const HomeScreen(),
|
|
kRingRoute: (_) => const RingScreen(),
|
|
},
|
|
);
|
|
}
|
|
}
|