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.
This commit is contained in:
Rijad Zuzo
2026-05-17 00:17:20 +02:00
parent dab50abf0e
commit f655adfbea
212 changed files with 24178 additions and 15895 deletions
+53 -37
View File
@@ -1,44 +1,60 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:relationship_saver/core/config/app_theme.dart';
import 'package:relationship_saver/features/auth/session_controller.dart';
import 'package:relationship_saver/features/auth/sign_in_view.dart';
import 'package:relationship_saver/features/home/app_shell.dart';
import 'package:relationship_saver/features/reminders/scheduling/reminder_notification_listener.dart';
import 'package:relationship_saver/features/share_intake/whatsapp_share_listener.dart';
import 'package:relationship_saver/features/sync/sync_background_runner.dart';
import 'package:relationship_saver/integrations/backend/models/backend_models.dart';
import 'package:relationship_saver/app/relationship_saver_app.dart';
import 'package:relationship_saver/core/observability/sentry_init.dart';
import 'package:relationship_saver/features/ai_digest/application/llm_digest_background_scheduler.dart';
import 'package:relationship_saver/features/ai_digest/application/llm_digest_orchestrator.dart';
import 'package:workmanager/workmanager.dart';
void main() {
runApp(const ProviderScope(child: RelationshipSaverApp()));
export 'package:relationship_saver/app/relationship_saver_app.dart';
@pragma('vm:entry-point')
void llmDigestCallbackDispatcher() {
Workmanager().executeTask((
String taskName,
Map<String, dynamic>? inputData,
) async {
if (taskName != llmDigestTaskName &&
taskName != Workmanager.iOSBackgroundTask) {
return true;
}
WidgetsFlutterBinding.ensureInitialized();
final ProviderContainer container = ProviderContainer();
try {
await container.read(llmDigestOrchestratorProvider).runScheduledDigest();
return true;
} finally {
container.dispose();
}
});
}
class RelationshipSaverApp extends ConsumerWidget {
const RelationshipSaverApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final AsyncValue<AuthSession?> sessionState = ref.watch(
sessionControllerProvider,
);
return MaterialApp(
title: 'Relationship Saver',
debugShowCheckedModeBanner: false,
theme: AppTheme.light(),
home: Scaffold(
body: sessionState.when(
data: (session) => session == null
? const SignInView()
: const WhatsAppShareListener(
child: ReminderNotificationListener(
child: SyncBackgroundRunner(child: AppShell()),
),
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (Object error, StackTrace stackTrace) => const SignInView(),
),
),
);
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
if (_supportsWorkmanagerPlatform()) {
await Workmanager().initialize(llmDigestCallbackDispatcher);
}
final SentryInit sentry = SentryInit();
await sentry.initialize(
appRunner: () {
runApp(
ProviderScope(
overrides: [sentryInitProvider.overrideWithValue(sentry)],
child: const RelationshipSaverApp(),
),
);
},
);
}
bool _supportsWorkmanagerPlatform() {
if (kIsWeb) {
return false;
}
return defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.android;
}