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:
+53
-37
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user