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
@@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:relationship_saver/features/local/local_models.dart';
import 'package:relationship_saver/features/people/presentation/people_view.dart';
import 'package:relationship_saver/features/people/presentation/providers/people_ui_state.dart';
import 'package:relationship_saver/features/share_intake/share_capture_models.dart';
import 'package:relationship_saver/features/share_intake/share_capture_review_sheet.dart';
import 'package:relationship_saver/features/share_intake/share_inbox_view.dart';
import 'package:relationship_saver/features/share_intake/share_payload_parser.dart';
SharedPayload? buildSharedPayloadFromRawText({
required String rawText,
required String platform,
String? sourceAppHint,
}) {
final String trimmed = rawText.trim();
if (trimmed.isEmpty) {
return null;
}
final String sourceApp = SharePayloadParser.detectSourceApp(
rawText: trimmed,
sourceAppHint: sourceAppHint,
);
final SharedPayload payload = SharePayloadParser.parse(
rawText: trimmed,
sourceApp: sourceApp,
platform: platform,
createdAt: DateTime.now(),
receivedAt: DateTime.now(),
);
if (payload.rawText.trim().isEmpty && (payload.url?.trim().isEmpty ?? true)) {
return null;
}
return payload;
}
Future<SharedMessageIngestResult?> startShareCaptureReview(
BuildContext context, {
required WidgetRef ref,
required SharedPayload payload,
}) {
return showShareCaptureReviewSheet(
context,
ref: ref,
payload: payload,
initialPersonId: ref.read(selectedPersonIdProvider),
);
}
void openShareCaptureDestination(
BuildContext context, {
required WidgetRef ref,
required SharedMessageIngestResult result,
}) {
if (result.isQueuedForResolution) {
Navigator.of(context).push<void>(
MaterialPageRoute<void>(
builder: (BuildContext context) => const ShareInboxView(),
),
);
return;
}
if (result.profileId != null) {
ref.read(selectedPersonIdProvider.notifier).select(result.profileId!);
}
Navigator.of(context).push<void>(
MaterialPageRoute<void>(
builder: (BuildContext context) => const PeopleView(),
),
);
}
String shareCaptureResultMessage(SharedMessageIngestResult result) {
if (result.isQueuedForResolution) {
return 'Saved shared capture to inbox.';
}
if (result.createdProfile) {
return 'Saved shared capture and created ${result.profileName}.';
}
return 'Saved shared capture to ${result.profileName}.';
}