Add related-screen navigation for sync rejections
This commit is contained in:
+20
-2
@@ -2,6 +2,24 @@
|
|||||||
|
|
||||||
Updated: 2026-02-15
|
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
|
## Latest Milestone (2026-02-15): Notification Permission UX
|
||||||
|
|
||||||
Added runtime notification permission flow through the product UI:
|
Added runtime notification permission flow through the product UI:
|
||||||
@@ -226,8 +244,8 @@ Validation for this milestone:
|
|||||||
- permission UX implemented in settings.
|
- permission UX implemented in settings.
|
||||||
- future enhancement: direct notification tap routing.
|
- future enhancement: direct notification tap routing.
|
||||||
2. Conflict resolution UX:
|
2. Conflict resolution UX:
|
||||||
- reject/requeue + payload inspect are implemented; future enhancement is
|
- reject/requeue + payload inspect + related-screen navigation implemented.
|
||||||
direct deep-link navigation to entity edit screens from Sync view.
|
- future enhancement: deep-link directly to specific entity edit form.
|
||||||
3. Sync trigger maturity:
|
3. Sync trigger maturity:
|
||||||
- implemented via reachability gating; future improvement is explicit OS
|
- implemented via reachability gating; future improvement is explicit OS
|
||||||
connectivity event subscription.
|
connectivity event subscription.
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import 'dart:convert';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:relationship_saver/core/config/app_theme.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/shared/frosted_card.dart';
|
||||||
import 'package:relationship_saver/features/sync/sync_coordinator.dart';
|
import 'package:relationship_saver/features/sync/sync_coordinator.dart';
|
||||||
import 'package:relationship_saver/features/sync/sync_queue_repository.dart';
|
import 'package:relationship_saver/features/sync/sync_queue_repository.dart';
|
||||||
@@ -207,6 +211,11 @@ class _SyncViewState extends ConsumerState<SyncView> {
|
|||||||
: () => _toggleRejectionExpanded(
|
: () => _toggleRejectionExpanded(
|
||||||
rejection.clientMutationId,
|
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 ChangeEnvelope? matched,
|
||||||
required bool expanded,
|
required bool expanded,
|
||||||
required VoidCallback? onToggleExpanded,
|
required VoidCallback? onToggleExpanded,
|
||||||
|
required VoidCallback? onOpenEntity,
|
||||||
}) {
|
}) {
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user