mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-08-30 22:50:50 +02:00
add changeset comment tracking and message notification bubble on startup
This commit is contained in:
@@ -18,6 +18,7 @@ import '../widgets/search_bar.dart';
|
||||
import '../widgets/suspected_location_sheet.dart';
|
||||
import '../widgets/welcome_dialog.dart';
|
||||
import '../widgets/changelog_dialog.dart';
|
||||
import '../widgets/unread_notifications_dialog.dart';
|
||||
import '../models/osm_node.dart';
|
||||
import '../models/suspected_location.dart';
|
||||
import '../models/search_result.dart';
|
||||
@@ -53,6 +54,9 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
|
||||
// Track popup display to avoid showing multiple times
|
||||
bool _hasCheckedForPopup = false;
|
||||
|
||||
// Track whether we've shown the unread notifications popup this session
|
||||
bool _hasShownUnreadNotificationsPopup = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -247,6 +251,21 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
}
|
||||
}
|
||||
|
||||
void _showUnreadNotificationsDialog() {
|
||||
if (!mounted) return;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => UnreadNotificationsDialog(
|
||||
onView: () {
|
||||
Navigator.of(context).pushNamed('/settings/osm-account');
|
||||
},
|
||||
onDismiss: () {
|
||||
// Just dismiss - badges remain visible until the user views them.
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onStartRoute() {
|
||||
_navigationCoordinator.startRoute(
|
||||
context: context,
|
||||
@@ -457,6 +476,19 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
});
|
||||
}
|
||||
|
||||
// Show a one-time popup this session if the user has unread messages
|
||||
// and/or unread changeset comments. Gated on _hasCheckedForPopup so it
|
||||
// doesn't race with the welcome/changelog dialog above.
|
||||
if (_hasCheckedForPopup &&
|
||||
!_hasShownUnreadNotificationsPopup &&
|
||||
appState.isLoggedIn &&
|
||||
appState.hasUnreadNotifications) {
|
||||
_hasShownUnreadNotificationsPopup = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_showUnreadNotificationsDialog();
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-focus a node's details sheet right after it was submitted/edited/deleted,
|
||||
// behind kAutoOpenNodeSheetAfterSubmit (pending A/B testing/team feedback).
|
||||
if (kAutoOpenNodeSheetAfterSubmit) {
|
||||
@@ -510,23 +542,9 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
final appState = context.watch<AppState>();
|
||||
return IconButton(
|
||||
tooltip: LocalizationService.instance.settings,
|
||||
icon: Stack(
|
||||
children: [
|
||||
const Icon(Icons.settings),
|
||||
if (appState.hasUnreadMessages)
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
child: Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
icon: Badge(
|
||||
isLabelVisible: appState.hasUnreadNotifications,
|
||||
child: const Icon(Icons.settings),
|
||||
),
|
||||
onPressed: () => Navigator.pushNamed(context, '/settings'),
|
||||
);
|
||||
|
||||
@@ -128,38 +128,46 @@ class _OSMAccountScreenState extends State<OSMAccountScreen> {
|
||||
},
|
||||
),
|
||||
|
||||
// Unread changeset comments - links directly to the
|
||||
// specific changeset with the new comment, and only
|
||||
// appears when there is one.
|
||||
if (appState.hasUnreadChangesetComments) ...[
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: Badge(
|
||||
isLabelVisible: true,
|
||||
child: const Icon(Icons.comment),
|
||||
),
|
||||
title: Text(locService.t('auth.newChangesetComments')),
|
||||
subtitle: Text(locService.t('auth.newChangesetCommentsSubtitle')),
|
||||
trailing: const Icon(Icons.open_in_new),
|
||||
onTap: () async {
|
||||
final changesetUrl = appState.getUnreadChangesetUrl();
|
||||
if (changesetUrl == null) return;
|
||||
final url = Uri.parse(changesetUrl);
|
||||
if (await canLaunchUrl(url)) {
|
||||
await launchUrl(url, mode: LaunchMode.externalApplication);
|
||||
// The user has now had the opportunity to see the
|
||||
// new comment on OSM's website - clear our badge.
|
||||
appState.markChangesetCommentsRead();
|
||||
} else {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(locService.t('advancedEdit.couldNotOpenOSMWebsite'))),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
// Messages button - only show when not in simulate mode
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: Stack(
|
||||
children: [
|
||||
const Icon(Icons.message),
|
||||
if (appState.hasUnreadMessages)
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 12,
|
||||
minHeight: 12,
|
||||
),
|
||||
child: Text(
|
||||
'${appState.unreadMessageCount}',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onError,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
leading: Badge(
|
||||
isLabelVisible: appState.hasUnreadMessages,
|
||||
label: Text('${appState.unreadMessageCount ?? 0}'),
|
||||
child: const Icon(Icons.message),
|
||||
),
|
||||
title: Text(locService.t('auth.viewMessages')),
|
||||
subtitle: Text(appState.hasUnreadMessages
|
||||
|
||||
@@ -117,23 +117,9 @@ class SettingsScreen extends StatelessWidget {
|
||||
final appState = context.watch<AppState>();
|
||||
|
||||
return ListTile(
|
||||
leading: Stack(
|
||||
children: [
|
||||
const Icon(Icons.account_circle),
|
||||
if (appState.hasUnreadMessages)
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
child: Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
leading: Badge(
|
||||
isLabelVisible: appState.hasUnreadNotifications,
|
||||
child: const Icon(Icons.account_circle),
|
||||
),
|
||||
title: Text(locService.t('auth.osmAccountTitle')),
|
||||
subtitle: Text(locService.t('auth.osmAccountSubtitle')),
|
||||
|
||||
Reference in New Issue
Block a user