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
@@ -9,45 +9,48 @@ import 'package:relationship_saver/features/sync/sync_background_runner.dart';
import 'package:relationship_saver/features/sync/sync_coordinator.dart';
void main() {
testWidgets('triggers sync when reachability transitions from offline to online', (
WidgetTester tester,
) async {
final _TestReachability reachability = _TestReachability(initial: false);
addTearDown(reachability.dispose);
testWidgets(
'triggers sync when reachability transitions from offline to online',
(WidgetTester tester) async {
final _TestReachability reachability = _TestReachability(initial: false);
addTearDown(reachability.dispose);
int syncCalls = 0;
int syncCalls = 0;
await tester.pumpWidget(
ProviderScope(
overrides: [
networkReachabilityProvider.overrideWithValue(reachability),
syncCoordinatorProvider.overrideWith(
(Ref ref) =>
_TestSyncCoordinator(ref, onSyncNow: () async {
await tester.pumpWidget(
ProviderScope(
overrides: [
networkReachabilityProvider.overrideWithValue(reachability),
syncCoordinatorProvider.overrideWith(
(Ref ref) => _TestSyncCoordinator(
ref,
onSyncNow: () async {
syncCalls += 1;
return _okResult();
}),
},
),
),
],
child: const Directionality(
textDirection: TextDirection.ltr,
child: SyncBackgroundRunner(child: SizedBox()),
),
],
child: const Directionality(
textDirection: TextDirection.ltr,
child: SyncBackgroundRunner(child: SizedBox()),
),
),
);
await tester.pumpAndSettle(const Duration(milliseconds: 60));
);
await tester.pumpAndSettle(const Duration(milliseconds: 60));
// Startup trigger always runs once when background sync is enabled.
expect(syncCalls, 1);
// Startup trigger always runs once when background sync is enabled.
expect(syncCalls, 1);
reachability.emit(false);
await tester.pumpAndSettle(const Duration(milliseconds: 40));
expect(syncCalls, 1);
reachability.emit(false);
await tester.pumpAndSettle(const Duration(milliseconds: 40));
expect(syncCalls, 1);
reachability.emit(true);
await tester.pumpAndSettle(const Duration(milliseconds: 60));
expect(syncCalls, 2);
});
reachability.emit(true);
await tester.pumpAndSettle(const Duration(milliseconds: 60));
expect(syncCalls, 2);
},
);
testWidgets('does not trigger extra sync while connectivity stays online', (
WidgetTester tester,
@@ -62,11 +65,13 @@ void main() {
overrides: [
networkReachabilityProvider.overrideWithValue(reachability),
syncCoordinatorProvider.overrideWith(
(Ref ref) =>
_TestSyncCoordinator(ref, onSyncNow: () async {
syncCalls += 1;
return _okResult();
}),
(Ref ref) => _TestSyncCoordinator(
ref,
onSyncNow: () async {
syncCalls += 1;
return _okResult();
},
),
),
],
child: const Directionality(
@@ -102,7 +107,9 @@ class _TestReachability implements NetworkReachability {
bool _current;
@override
Future<bool> isReachable({Duration timeout = const Duration(seconds: 2)}) async {
Future<bool> isReachable({
Duration timeout = const Duration(seconds: 2),
}) async {
return _current;
}