Files
rely/lib/features/sync/application/sync_background_runner.dart
T
Rijad Zuzo f655adfbea feat: add local-first private AI digest workflow
Migrate app code into canonical feature slices, add phone-only AI digest scheduling and review, wire local notification/background task support, and cover the flow with tests.
2026-05-17 00:17:20 +02:00

108 lines
3.2 KiB
Dart

import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:relationship_saver/core/config/app_config.dart';
import 'package:relationship_saver/core/network/reachability/network_reachability.dart';
import 'package:relationship_saver/core/network/reachability/network_reachability_provider.dart';
import 'package:relationship_saver/features/sync/sync_auto_trigger_controller.dart';
import 'package:relationship_saver/features/sync/sync_coordinator.dart';
/// Runs lightweight background sync triggers while authenticated.
class SyncBackgroundRunner extends ConsumerStatefulWidget {
const SyncBackgroundRunner({required this.child, super.key});
final Widget child;
@override
ConsumerState<SyncBackgroundRunner> createState() =>
_SyncBackgroundRunnerState();
}
class _SyncBackgroundRunnerState extends ConsumerState<SyncBackgroundRunner>
with WidgetsBindingObserver {
late final SyncAutoTriggerController _controller;
Timer? _periodicTimer;
StreamSubscription<bool>? _reachabilitySubscription;
bool? _lastReachable;
@override
void initState() {
super.initState();
final reachability = ref.read(networkReachabilityProvider);
_controller = SyncAutoTriggerController(
syncNow: () => ref.read(syncCoordinatorProvider).syncNow(),
now: DateTime.now,
minimumInterval: Duration(
seconds: AppConfig.backgroundSyncIntervalSeconds,
),
canTrigger: () async {
if (AppConfig.useFakeBackend) {
return true;
}
return reachability.isReachable();
},
);
WidgetsBinding.instance.addObserver(this);
_configurePeriodicRunner();
_configureReachabilityRunner(reachability);
if (AppConfig.enableBackgroundSync) {
unawaited(_controller.trigger(ignoreCooldown: true));
}
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (!AppConfig.enableBackgroundSync) {
return;
}
if (state == AppLifecycleState.resumed) {
unawaited(_controller.trigger(ignoreCooldown: true));
}
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_periodicTimer?.cancel();
_reachabilitySubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) => widget.child;
void _configurePeriodicRunner() {
if (!AppConfig.enableBackgroundSync) {
return;
}
final int seconds = AppConfig.backgroundSyncIntervalSeconds;
if (seconds <= 0) {
return;
}
_periodicTimer = Timer.periodic(Duration(seconds: seconds), (_) {
unawaited(_controller.trigger());
});
}
void _configureReachabilityRunner(NetworkReachability reachability) {
if (!AppConfig.enableBackgroundSync) {
return;
}
_reachabilitySubscription = reachability.watch().listen((bool reachable) {
final bool? previous = _lastReachable;
_lastReachable = reachable;
if (reachable && previous == false) {
unawaited(_controller.trigger(ignoreCooldown: true));
}
});
unawaited(_primeReachabilityState(reachability));
}
Future<void> _primeReachabilityState(NetworkReachability reachability) async {
_lastReachable = await reachability.isReachable();
}
}