fix: migrate filter menus to type-safe generics and harden popup dismissal

- Move ContactSortOption/ContactTypeFilter enums to dedicated
  contact_filter_types.dart (re-exported from contact_search.dart)
- Migrate ContactsFilterMenu and DiscoveryContactsFilterMenu to use
  sealed class action types with SortFilterMenu<T> generics, replacing
  int action constants and switch statements
- Guard _closeDropdownAndRun with ModalRoute.isCurrent check to prevent
  accidental dismissal of parent routes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
chore/offband-rebrand
zjs81 4 months ago
parent 86e9b7fe01
commit 566e3aadf8

@ -454,8 +454,11 @@ class _ContactsScreenState extends State<ContactsScreen>
} }
} }
void _closeDropdownAndRun(BuildContext context, VoidCallback action) { void _closeDropdownAndRun(BuildContext popupContext, VoidCallback action) {
Navigator.of(context).pop(); final route = ModalRoute.of(popupContext);
if (route != null && route.isCurrent) {
Navigator.of(popupContext).pop();
}
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; if (!mounted) return;
action(); action();

@ -0,0 +1,3 @@
enum ContactSortOption { lastSeen, recentMessages, name }
enum ContactTypeFilter { all, favorites, users, repeaters, rooms }

@ -1,8 +1,6 @@
import '../models/contact.dart'; import '../models/contact.dart';
enum ContactSortOption { lastSeen, recentMessages, name } export 'contact_filter_types.dart';
enum ContactTypeFilter { all, favorites, users, repeaters, rooms }
bool matchesContactQuery(Contact contact, String query) { bool matchesContactQuery(Contact contact, String query) {
final normalizedQuery = query.trim().toLowerCase(); final normalizedQuery = query.trim().toLowerCase();

@ -87,15 +87,23 @@ class SortFilterMenu<T> extends StatelessWidget {
} }
} }
const int _actionSortRecentMessages = 1; sealed class _ContactsFilterAction {
const int _actionSortName = 2; const _ContactsFilterAction();
const int _actionSortLastSeen = 3; }
const int _actionFilterAll = 4;
const int _actionFilterFavorites = 5; class _SortAction extends _ContactsFilterAction {
const int _actionFilterUsers = 6; final ContactSortOption option;
const int _actionFilterRepeaters = 7; const _SortAction(this.option);
const int _actionFilterRooms = 8; }
const int _actionToggleUnreadOnly = 9;
class _TypeFilterAction extends _ContactsFilterAction {
final ContactTypeFilter filter;
const _TypeFilterAction(this.filter);
}
class _ToggleUnreadAction extends _ContactsFilterAction {
const _ToggleUnreadAction();
}
class ContactsFilterMenu extends StatelessWidget { class ContactsFilterMenu extends StatelessWidget {
final ContactSortOption sortOption; final ContactSortOption sortOption;
@ -118,24 +126,24 @@ class ContactsFilterMenu extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
return SortFilterMenu( return SortFilterMenu<_ContactsFilterAction>(
tooltip: l10n.listFilter_tooltip, tooltip: l10n.listFilter_tooltip,
sections: [ sections: [
SortFilterMenuSection( SortFilterMenuSection(
title: l10n.listFilter_sortBy, title: l10n.listFilter_sortBy,
options: [ options: [
SortFilterMenuOption( SortFilterMenuOption(
value: _actionSortRecentMessages, value: _SortAction(ContactSortOption.recentMessages),
label: l10n.listFilter_latestMessages, label: l10n.listFilter_latestMessages,
checked: sortOption == ContactSortOption.recentMessages, checked: sortOption == ContactSortOption.recentMessages,
), ),
SortFilterMenuOption( SortFilterMenuOption(
value: _actionSortLastSeen, value: _SortAction(ContactSortOption.lastSeen),
label: l10n.listFilter_heardRecently, label: l10n.listFilter_heardRecently,
checked: sortOption == ContactSortOption.lastSeen, checked: sortOption == ContactSortOption.lastSeen,
), ),
SortFilterMenuOption( SortFilterMenuOption(
value: _actionSortName, value: _SortAction(ContactSortOption.name),
label: l10n.listFilter_az, label: l10n.listFilter_az,
checked: sortOption == ContactSortOption.name, checked: sortOption == ContactSortOption.name,
), ),
@ -145,32 +153,32 @@ class ContactsFilterMenu extends StatelessWidget {
title: l10n.listFilter_filters, title: l10n.listFilter_filters,
options: [ options: [
SortFilterMenuOption( SortFilterMenuOption(
value: _actionFilterAll, value: _TypeFilterAction(ContactTypeFilter.all),
label: l10n.listFilter_all, label: l10n.listFilter_all,
checked: typeFilter == ContactTypeFilter.all, checked: typeFilter == ContactTypeFilter.all,
), ),
SortFilterMenuOption( SortFilterMenuOption(
value: _actionFilterFavorites, value: _TypeFilterAction(ContactTypeFilter.favorites),
label: l10n.listFilter_favorites, label: l10n.listFilter_favorites,
checked: typeFilter == ContactTypeFilter.favorites, checked: typeFilter == ContactTypeFilter.favorites,
), ),
SortFilterMenuOption( SortFilterMenuOption(
value: _actionFilterUsers, value: _TypeFilterAction(ContactTypeFilter.users),
label: l10n.listFilter_users, label: l10n.listFilter_users,
checked: typeFilter == ContactTypeFilter.users, checked: typeFilter == ContactTypeFilter.users,
), ),
SortFilterMenuOption( SortFilterMenuOption(
value: _actionFilterRepeaters, value: _TypeFilterAction(ContactTypeFilter.repeaters),
label: l10n.listFilter_repeaters, label: l10n.listFilter_repeaters,
checked: typeFilter == ContactTypeFilter.repeaters, checked: typeFilter == ContactTypeFilter.repeaters,
), ),
SortFilterMenuOption( SortFilterMenuOption(
value: _actionFilterRooms, value: _TypeFilterAction(ContactTypeFilter.rooms),
label: l10n.listFilter_roomServers, label: l10n.listFilter_roomServers,
checked: typeFilter == ContactTypeFilter.rooms, checked: typeFilter == ContactTypeFilter.rooms,
), ),
SortFilterMenuOption( SortFilterMenuOption(
value: _actionToggleUnreadOnly, value: const _ToggleUnreadAction(),
label: l10n.listFilter_unreadOnly, label: l10n.listFilter_unreadOnly,
checked: showUnreadOnly, checked: showUnreadOnly,
), ),
@ -179,39 +187,32 @@ class ContactsFilterMenu extends StatelessWidget {
], ],
onSelected: (action) { onSelected: (action) {
switch (action) { switch (action) {
case _actionSortRecentMessages: case _SortAction(:final option):
onSortChanged(ContactSortOption.recentMessages); onSortChanged(option);
break; case _TypeFilterAction(:final filter):
case _actionSortName: onTypeFilterChanged(filter);
onSortChanged(ContactSortOption.name); case _ToggleUnreadAction():
break;
case _actionSortLastSeen:
onSortChanged(ContactSortOption.lastSeen);
break;
case _actionFilterAll:
onTypeFilterChanged(ContactTypeFilter.all);
break;
case _actionFilterUsers:
onTypeFilterChanged(ContactTypeFilter.users);
break;
case _actionFilterFavorites:
onTypeFilterChanged(ContactTypeFilter.favorites);
break;
case _actionFilterRepeaters:
onTypeFilterChanged(ContactTypeFilter.repeaters);
break;
case _actionFilterRooms:
onTypeFilterChanged(ContactTypeFilter.rooms);
break;
case _actionToggleUnreadOnly:
onUnreadOnlyChanged(!showUnreadOnly); onUnreadOnlyChanged(!showUnreadOnly);
break;
} }
}, },
); );
} }
} }
sealed class _DiscoveryFilterAction {
const _DiscoveryFilterAction();
}
class _DiscoverySortAction extends _DiscoveryFilterAction {
final ContactSortOption option;
const _DiscoverySortAction(this.option);
}
class _DiscoveryTypeFilterAction extends _DiscoveryFilterAction {
final ContactTypeFilter filter;
const _DiscoveryTypeFilterAction(this.filter);
}
class DiscoveryContactsFilterMenu extends StatelessWidget { class DiscoveryContactsFilterMenu extends StatelessWidget {
final ContactSortOption sortOption; final ContactSortOption sortOption;
final ContactTypeFilter typeFilter; final ContactTypeFilter typeFilter;
@ -229,19 +230,19 @@ class DiscoveryContactsFilterMenu extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
return SortFilterMenu( return SortFilterMenu<_DiscoveryFilterAction>(
tooltip: l10n.listFilter_tooltip, tooltip: l10n.listFilter_tooltip,
sections: [ sections: [
SortFilterMenuSection( SortFilterMenuSection(
title: l10n.listFilter_sortBy, title: l10n.listFilter_sortBy,
options: [ options: [
SortFilterMenuOption( SortFilterMenuOption(
value: _actionSortLastSeen, value: _DiscoverySortAction(ContactSortOption.lastSeen),
label: l10n.listFilter_heardRecently, label: l10n.listFilter_heardRecently,
checked: sortOption == ContactSortOption.lastSeen, checked: sortOption == ContactSortOption.lastSeen,
), ),
SortFilterMenuOption( SortFilterMenuOption(
value: _actionSortName, value: _DiscoverySortAction(ContactSortOption.name),
label: l10n.listFilter_az, label: l10n.listFilter_az,
checked: sortOption == ContactSortOption.name, checked: sortOption == ContactSortOption.name,
), ),
@ -251,22 +252,22 @@ class DiscoveryContactsFilterMenu extends StatelessWidget {
title: l10n.listFilter_filters, title: l10n.listFilter_filters,
options: [ options: [
SortFilterMenuOption( SortFilterMenuOption(
value: _actionFilterAll, value: _DiscoveryTypeFilterAction(ContactTypeFilter.all),
label: l10n.listFilter_all, label: l10n.listFilter_all,
checked: typeFilter == ContactTypeFilter.all, checked: typeFilter == ContactTypeFilter.all,
), ),
SortFilterMenuOption( SortFilterMenuOption(
value: _actionFilterUsers, value: _DiscoveryTypeFilterAction(ContactTypeFilter.users),
label: l10n.listFilter_users, label: l10n.listFilter_users,
checked: typeFilter == ContactTypeFilter.users, checked: typeFilter == ContactTypeFilter.users,
), ),
SortFilterMenuOption( SortFilterMenuOption(
value: _actionFilterRepeaters, value: _DiscoveryTypeFilterAction(ContactTypeFilter.repeaters),
label: l10n.listFilter_repeaters, label: l10n.listFilter_repeaters,
checked: typeFilter == ContactTypeFilter.repeaters, checked: typeFilter == ContactTypeFilter.repeaters,
), ),
SortFilterMenuOption( SortFilterMenuOption(
value: _actionFilterRooms, value: _DiscoveryTypeFilterAction(ContactTypeFilter.rooms),
label: l10n.listFilter_roomServers, label: l10n.listFilter_roomServers,
checked: typeFilter == ContactTypeFilter.rooms, checked: typeFilter == ContactTypeFilter.rooms,
), ),
@ -275,27 +276,10 @@ class DiscoveryContactsFilterMenu extends StatelessWidget {
], ],
onSelected: (action) { onSelected: (action) {
switch (action) { switch (action) {
case _actionSortName: case _DiscoverySortAction(:final option):
onSortChanged(ContactSortOption.name); onSortChanged(option);
break; case _DiscoveryTypeFilterAction(:final filter):
case _actionSortLastSeen: onTypeFilterChanged(filter);
onSortChanged(ContactSortOption.lastSeen);
break;
case _actionFilterAll:
onTypeFilterChanged(ContactTypeFilter.all);
break;
case _actionFilterUsers:
onTypeFilterChanged(ContactTypeFilter.users);
break;
case _actionFilterFavorites:
onTypeFilterChanged(ContactTypeFilter.favorites);
break;
case _actionFilterRepeaters:
onTypeFilterChanged(ContactTypeFilter.repeaters);
break;
case _actionFilterRooms:
onTypeFilterChanged(ContactTypeFilter.rooms);
break;
} }
}, },
); );

Loading…
Cancel
Save

Powered by TurnKey Linux.