Add related-screen navigation for sync rejections

This commit is contained in:
Rijad Zuzo
2026-02-15 23:56:08 +01:00
parent 560716100d
commit c20c29550a
2 changed files with 111 additions and 2 deletions
+20 -2
View File
@@ -2,6 +2,24 @@
Updated: 2026-02-15
## Latest Milestone (2026-02-15): Sync Rejection Repair Navigation
Added direct repair navigation from rejected sync items:
- Updated `lib/features/sync/sync_view.dart`:
- each rejected item now includes `Open Related Screen`
- routes user to a relevant maintenance screen based on entity type:
- `person` -> People
- `capture` -> Moments
- `giftIdea`/`eventIdea` -> Ideas
- `reminderRule` -> Reminders
- keeps existing payload inspect + requeue/dismiss actions
Validation for this milestone:
- `flutter analyze` -> pass
- `flutter test` -> pass
## Latest Milestone (2026-02-15): Notification Permission UX
Added runtime notification permission flow through the product UI:
@@ -226,8 +244,8 @@ Validation for this milestone:
- permission UX implemented in settings.
- future enhancement: direct notification tap routing.
2. Conflict resolution UX:
- reject/requeue + payload inspect are implemented; future enhancement is
direct deep-link navigation to entity edit screens from Sync view.
- reject/requeue + payload inspect + related-screen navigation implemented.
- future enhancement: deep-link directly to specific entity edit form.
3. Sync trigger maturity:
- implemented via reachability gating; future improvement is explicit OS
connectivity event subscription.
+91
View File
@@ -3,6 +3,10 @@ import 'dart:convert';
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/ideas/ideas_view.dart';
import 'package:relationship_saver/features/moments/moments_view.dart';
import 'package:relationship_saver/features/people/people_view.dart';
import 'package:relationship_saver/features/reminders/reminders_view.dart';
import 'package:relationship_saver/features/shared/frosted_card.dart';
import 'package:relationship_saver/features/sync/sync_coordinator.dart';
import 'package:relationship_saver/features/sync/sync_queue_repository.dart';
@@ -207,6 +211,11 @@ class _SyncViewState extends ConsumerState<SyncView> {
: () => _toggleRejectionExpanded(
rejection.clientMutationId,
),
onOpenEntity:
matched != null &&
_entityViewForType(matched.entityType) != null
? () => _openEntityViewFor(matched)
: null,
),
);
}),
@@ -273,6 +282,7 @@ class _SyncViewState extends ConsumerState<SyncView> {
required ChangeEnvelope? matched,
required bool expanded,
required VoidCallback? onToggleExpanded,
required VoidCallback? onOpenEntity,
}) {
return Container(
width: double.infinity,
@@ -348,6 +358,19 @@ class _SyncViewState extends ConsumerState<SyncView> {
),
),
],
if (onOpenEntity != null) ...<Widget>[
const SizedBox(height: 8),
Wrap(
spacing: 8,
children: <Widget>[
TextButton.icon(
onPressed: onOpenEntity,
icon: const Icon(Icons.open_in_new_rounded),
label: const Text('Open Related Screen'),
),
],
),
],
],
),
);
@@ -486,4 +509,72 @@ class _SyncViewState extends ConsumerState<SyncView> {
}
});
}
Future<void> _openEntityViewFor(ChangeEnvelope matched) async {
final Widget? view = _entityViewForType(matched.entityType);
if (view == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('No editor available for ${matched.entityType}.'),
),
);
return;
}
await Navigator.of(context).push<void>(
MaterialPageRoute<void>(
builder: (BuildContext context) => _EntityResolveScreen(
title: _titleForEntityType(matched.entityType),
child: view,
),
),
);
}
Widget? _entityViewForType(String entityType) {
switch (entityType) {
case 'person':
return const PeopleView();
case 'capture':
return const MomentsView();
case 'giftIdea':
case 'eventIdea':
return const IdeasView();
case 'reminderRule':
return const RemindersView();
default:
return null;
}
}
String _titleForEntityType(String entityType) {
switch (entityType) {
case 'person':
return 'Resolve Person';
case 'capture':
return 'Resolve Moment';
case 'giftIdea':
case 'eventIdea':
return 'Resolve Idea';
case 'reminderRule':
return 'Resolve Reminder';
default:
return 'Resolve Item';
}
}
}
class _EntityResolveScreen extends StatelessWidget {
const _EntityResolveScreen({required this.title, required this.child});
final String title;
final Widget child;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: child,
);
}
}