import 'dart:async'; import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:relationship_saver/features/ai_digest/data/llm_digest_config.dart'; import 'package:relationship_saver/features/ai_digest/domain/ai_digest_models.dart'; import 'package:workmanager/workmanager.dart'; const String llmDigestTaskUniqueName = 'relationship_saver_llm_digest_weekly'; const String llmDigestTaskName = 'com.relationshipsaver.llm.digest'; abstract interface class LlmDigestBackgroundScheduler { Future configure(LlmDigestConfigState config); Future cancel(); } class WorkmanagerLlmDigestBackgroundScheduler implements LlmDigestBackgroundScheduler { const WorkmanagerLlmDigestBackgroundScheduler({Workmanager? workmanager}) : _workmanager = workmanager; final Workmanager? _workmanager; Workmanager get _driver => _workmanager ?? Workmanager(); @override Future configure(LlmDigestConfigState config) async { if (!config.enabled) { await cancel(); return; } try { await _driver.registerPeriodicTask( llmDigestTaskUniqueName, llmDigestTaskName, frequency: const Duration(days: 7), initialDelay: _initialDelay(config, DateTime.now()), constraints: Constraints( networkType: config.requireWifi ? NetworkType.unmetered : NetworkType.connected, requiresCharging: config.requireCharging, ), existingWorkPolicy: ExistingPeriodicWorkPolicy.update, ); } on UnimplementedError { // Flutter tests and unsupported platforms have no Workmanager backend. } } @override Future cancel() async { try { await _driver.cancelByUniqueName(llmDigestTaskUniqueName); } on UnimplementedError { // Flutter tests and unsupported platforms have no Workmanager backend. } } Duration _initialDelay(LlmDigestConfigState config, DateTime now) { DateTime target = DateTime( now.year, now.month, now.day, config.preferredHour, ); final int dayDelta = (config.preferredWeekday - now.weekday) % 7; target = target.add(Duration(days: dayDelta)); if (!target.isAfter(now)) { target = target.add(const Duration(days: 7)); } return target.difference(now); } } final Provider llmDigestBackgroundSchedulerProvider = Provider( (Ref ref) => const WorkmanagerLlmDigestBackgroundScheduler(), ); class LlmDigestBackgroundRunner extends ConsumerStatefulWidget { const LlmDigestBackgroundRunner({required this.child, super.key}); final Widget child; @override ConsumerState createState() => _LlmDigestBackgroundRunnerState(); } class _LlmDigestBackgroundRunnerState extends ConsumerState { ProviderSubscription? _subscription; @override void initState() { super.initState(); unawaited(_initializeAndConfigure()); _subscription = ref.listenManual( llmDigestConfigProvider, (LlmDigestConfigState? previous, LlmDigestConfigState next) { unawaited( ref.read(llmDigestBackgroundSchedulerProvider).configure(next), ); }, ); } Future _initializeAndConfigure() async { await ref.read(llmDigestConfigProvider.notifier).initialize(); await ref .read(llmDigestBackgroundSchedulerProvider) .configure(ref.read(llmDigestConfigProvider)); } @override void dispose() { _subscription?.close(); super.dispose(); } @override Widget build(BuildContext context) => widget.child; }