diff --git a/docs/progress.md b/docs/progress.md index f0a9caa..cc3e9da 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -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. diff --git a/lib/features/sync/sync_view.dart b/lib/features/sync/sync_view.dart index 41bd3ac..0d297e7 100644 --- a/lib/features/sync/sync_view.dart +++ b/lib/features/sync/sync_view.dart @@ -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 { : () => _toggleRejectionExpanded( rejection.clientMutationId, ), + onOpenEntity: + matched != null && + _entityViewForType(matched.entityType) != null + ? () => _openEntityViewFor(matched) + : null, ), ); }), @@ -273,6 +282,7 @@ class _SyncViewState extends ConsumerState { 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 { ), ), ], + if (onOpenEntity != null) ...[ + const SizedBox(height: 8), + Wrap( + spacing: 8, + children: [ + 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 { } }); } + + Future _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( + MaterialPageRoute( + 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, + ); + } }