fix: Align contact sorting labels #0

This commit is contained in:
Janez T
2026-03-19 14:06:05 +01:00
parent e474168ba8
commit 8da69705ee
11 changed files with 1952 additions and 778 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -28,6 +28,7 @@ import '../services/voice_recorder_service.dart';
import '../services/voice_codec_service.dart';
import '../utils/toast_logger.dart';
import '../utils/key_comparison.dart';
import '../utils/contact_sorting.dart';
import '../utils/voice_message_parser.dart';
import '../utils/image_message_parser.dart';
import '../utils/tictactoe_message_parser.dart';
@@ -299,27 +300,27 @@ class _MessagesTabState extends State<MessagesTab> {
final contactsProvider = context.read<ContactsProvider>();
final normalizedQuery = query.trim().toLowerCase();
final contacts =
contactsProvider.contacts
.where((contact) => contact.type == ContactType.chat)
.where((contact) {
if (normalizedQuery.isEmpty) return true;
return contact.displayName.toLowerCase().contains(
normalizedQuery,
);
})
.toList()
..sort((a, b) {
final aName = a.displayName.toLowerCase();
final bName = b.displayName.toLowerCase();
final aStarts =
normalizedQuery.isNotEmpty && aName.startsWith(normalizedQuery);
final bStarts =
normalizedQuery.isNotEmpty && bName.startsWith(normalizedQuery);
if (aStarts != bStarts) {
return aStarts ? -1 : 1;
}
return aName.compareTo(bName);
});
contactsProvider.chatContacts.where((contact) {
if (normalizedQuery.isEmpty) return true;
return contact.displayName.toLowerCase().contains(normalizedQuery);
}).toList()..sort((a, b) {
final primary = compareContactsByFavouriteThenLastSeen(a, b);
if (primary != 0) {
return primary;
}
final aName = a.displayName.toLowerCase();
final bName = b.displayName.toLowerCase();
final aStarts =
normalizedQuery.isNotEmpty && aName.startsWith(normalizedQuery);
final bStarts =
normalizedQuery.isNotEmpty && bName.startsWith(normalizedQuery);
if (aStarts != bStarts) {
return aStarts ? -1 : 1;
}
return compareContactsByDisplayName(a, b);
});
return contacts.take(8).toList(growable: false);
}
@@ -408,16 +409,33 @@ class _MessagesTabState extends State<MessagesTab> {
final contactsProvider = context.read<ContactsProvider>();
final messagesProvider = context.read<MessagesProvider>();
// Filter contacts by type
final contacts = contactsProvider.contacts
.where((c) => c.type == ContactType.chat)
.toList();
final rooms = contactsProvider.contacts
.where((c) => c.type == ContactType.room)
.toList();
final channels = contactsProvider.contacts
.where((c) => c.type == ContactType.channel)
.toList();
final contacts = List<Contact>.from(contactsProvider.chatContacts)
..sort((a, b) {
final primary = compareContactsByFavouriteThenLastSeen(a, b);
if (primary != 0) {
return primary;
}
return compareContactsByDisplayName(a, b);
});
final rooms = List<Contact>.from(contactsProvider.rooms)
..sort((a, b) {
final primary = compareContactsByLastSeen(a, b);
if (primary != 0) {
return primary;
}
return compareContactsByDisplayName(a, b);
});
final channels = List<Contact>.from(contactsProvider.channels)
..sort((a, b) {
final primary = compareContactsByLastSeen(a, b);
if (primary != 0) {
return primary;
}
return compareContactsByDisplayName(a, b);
});
showModalBottomSheet(
context: context,
@@ -534,7 +552,10 @@ class _MessagesTabState extends State<MessagesTab> {
}).firstOrNull;
if (recipient == null) {
ToastLogger.error(context, AppLocalizations.of(context)!.cannotReplyContactNotFound);
ToastLogger.error(
context,
AppLocalizations.of(context)!.cannotReplyContactNotFound,
);
return;
}
}
@@ -563,13 +584,19 @@ class _MessagesTabState extends State<MessagesTab> {
} else {
final senderPrefix = message.senderPublicKeyPrefix;
if (senderPrefix == null || senderPrefix.length < 6) {
ToastLogger.error(context, AppLocalizations.of(context)!.cannotReplySenderMissing);
ToastLogger.error(
context,
AppLocalizations.of(context)!.cannotReplySenderMissing,
);
return;
}
recipient = contactsProvider.findContactByPrefix(senderPrefix);
if (recipient == null) {
ToastLogger.error(context, AppLocalizations.of(context)!.cannotReplyContactNotFound);
ToastLogger.error(
context,
AppLocalizations.of(context)!.cannotReplyContactNotFound,
);
return;
}

View File

@@ -190,9 +190,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
_isDeveloperModeEnabled = false;
_versionTapCount = 0;
});
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.developerModeDisabled)));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.developerModeDisabled),
),
);
return;
}
@@ -204,9 +206,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
_isDeveloperModeEnabled = true;
_versionTapCount = 0;
});
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.developerModeEnabled)));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.developerModeEnabled),
),
);
return;
}
@@ -559,7 +563,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.updateCheckIsOnlyAvailableOnAndroid),
content: Text(
AppLocalizations.of(context)!.updateCheckIsOnlyAvailableOnAndroid,
),
backgroundColor: Colors.orange,
),
);
@@ -584,7 +590,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
if (!updateInfo.isAvailable) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.youAreRunningTheLatestVersion),
content: Text(
AppLocalizations.of(context)!.youAreRunningTheLatestVersion,
),
backgroundColor: Colors.green,
),
);
@@ -594,7 +602,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
if (updateInfo.downloadUrl == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.updateAvailableButDownloadUrlNotFound),
content: Text(
AppLocalizations.of(
context,
)!.updateAvailableButDownloadUrlNotFound,
),
backgroundColor: Colors.orange,
),
);
@@ -1178,6 +1190,100 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
]),
_buildSectionHeader(AppLocalizations.of(context)!.contacts),
Consumer<AppProvider>(
builder: (context, appProvider, child) => _buildSettingsCard([
SwitchListTile(
secondary: const Icon(Icons.star_outline_rounded),
title: Text(AppLocalizations.of(context)!.favourites),
subtitle: const Text(
'Show the favourites section in the contacts tab',
),
value: appProvider.isContactsSectionEnabled(
ContactsTabSection.favourites,
),
onChanged: (value) async {
await appProvider.setContactsSectionEnabled(
ContactsTabSection.favourites,
value,
);
},
),
SwitchListTile(
secondary: const Icon(Icons.people_alt_outlined),
title: Text(AppLocalizations.of(context)!.teamMembers),
subtitle: const Text(
'Show direct team contacts in the contacts tab',
),
value: appProvider.isContactsSectionEnabled(
ContactsTabSection.teamMembers,
),
onChanged: (value) async {
await appProvider.setContactsSectionEnabled(
ContactsTabSection.teamMembers,
value,
);
},
),
SwitchListTile(
secondary: const Icon(Icons.router_outlined),
title: Text(AppLocalizations.of(context)!.repeaters),
subtitle: const Text('Show repeater nodes in the contacts tab'),
value: appProvider.isContactsSectionEnabled(
ContactsTabSection.repeaters,
),
onChanged: (value) async {
await appProvider.setContactsSectionEnabled(
ContactsTabSection.repeaters,
value,
);
},
),
SwitchListTile(
secondary: const Icon(Icons.sensors_outlined),
title: Text(AppLocalizations.of(context)!.sensors),
subtitle: const Text('Show sensor nodes in the contacts tab'),
value: appProvider.isContactsSectionEnabled(
ContactsTabSection.sensors,
),
onChanged: (value) async {
await appProvider.setContactsSectionEnabled(
ContactsTabSection.sensors,
value,
);
},
),
SwitchListTile(
secondary: const Icon(Icons.meeting_room_outlined),
title: Text(AppLocalizations.of(context)!.rooms),
subtitle: const Text('Show rooms in the contacts tab'),
value: appProvider.isContactsSectionEnabled(
ContactsTabSection.rooms,
),
onChanged: (value) async {
await appProvider.setContactsSectionEnabled(
ContactsTabSection.rooms,
value,
);
},
),
SwitchListTile(
secondary: const Icon(Icons.broadcast_on_personal_outlined),
title: Text(AppLocalizations.of(context)!.channels),
subtitle: const Text('Show channels in the contacts tab'),
value: appProvider.isContactsSectionEnabled(
ContactsTabSection.channels,
),
onChanged: (value) async {
await appProvider.setContactsSectionEnabled(
ContactsTabSection.channels,
value,
);
},
),
]),
),
_buildSectionHeader('Messaging'),
_buildSettingsCard([
ListTile(
@@ -1205,7 +1311,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
Consumer<AppProvider>(
builder: (context, appProvider, child) => SwitchListTile(
secondary: Icon(Icons.route),
title: Text(AppLocalizations.of(context)!.nearestRepeaterFallback),
title: Text(
AppLocalizations.of(context)!.nearestRepeaterFallback,
),
subtitle: const Text(
'After normal retries fail, try one final resend through the nearest repeater',
),
@@ -1234,7 +1342,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
'Clear Messages',
style: TextStyle(color: Colors.red),
),
subtitle: Text(AppLocalizations.of(context)!.deleteAllStoredMessageHistory),
subtitle: Text(
AppLocalizations.of(context)!.deleteAllStoredMessageHistory,
),
onTap: _clearMessages,
),
Consumer<AppProvider>(
@@ -1344,7 +1454,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
builder: (context, drawingProvider, child) => SwitchListTile(
secondary: Icon(Icons.fmd_good_outlined),
title: Text(AppLocalizations.of(context)!.showSarMarkersLabel),
subtitle: Text(AppLocalizations.of(context)!.displaySarMarkersOnTheMainMap),
subtitle: Text(
AppLocalizations.of(context)!.displaySarMarkersOnTheMainMap,
),
value: drawingProvider.showSarMarkers,
onChanged: (value) {
drawingProvider.toggleSarMarkers();
@@ -1354,7 +1466,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
Consumer<MapProvider>(
builder: (context, mapProvider, child) => SwitchListTile(
secondary: Icon(Icons.timeline),
title: Text(AppLocalizations.of(context)!.showAllContactTrailsLabel),
title: Text(
AppLocalizations.of(context)!.showAllContactTrailsLabel,
),
subtitle: const Text(
'Display location trails for all contacts that have history',
),
@@ -1420,7 +1534,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
builder: (context, appProvider, child) => SwitchListTile(
secondary: Icon(Icons.compress),
title: Text(AppLocalizations.of(context)!.voiceCompressor),
subtitle: Text(AppLocalizations.of(context)!.balancesQuietAndLoudSpeechLevels),
subtitle: Text(
AppLocalizations.of(
context,
)!.balancesQuietAndLoudSpeechLevels,
),
value: appProvider.isVoiceCompressorEnabled,
onChanged: (value) async {
await appProvider.toggleVoiceCompressorEnabled(value);
@@ -1431,7 +1549,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
builder: (context, appProvider, child) => SwitchListTile(
secondary: Icon(Icons.speed),
title: Text(AppLocalizations.of(context)!.voiceLimiter),
subtitle: Text(AppLocalizations.of(context)!.preventsClippingPeaksBeforeEncoding),
subtitle: Text(
AppLocalizations.of(
context,
)!.preventsClippingPeaksBeforeEncoding,
),
value: appProvider.isVoiceLimiterEnabled,
onChanged: (value) async {
await appProvider.toggleVoiceLimiterEnabled(value);
@@ -1442,7 +1564,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
builder: (context, appProvider, child) => SwitchListTile(
secondary: Icon(Icons.auto_fix_high),
title: Text(AppLocalizations.of(context)!.micAutoGain),
subtitle: Text(AppLocalizations.of(context)!.letsTheRecorderAdjustInputLevel),
subtitle: Text(
AppLocalizations.of(context)!.letsTheRecorderAdjustInputLevel,
),
value: appProvider.isVoiceAutoGainEnabled,
onChanged: (value) async {
await appProvider.toggleVoiceAutoGainEnabled(value);
@@ -1478,7 +1602,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
Consumer<AppProvider>(
builder: (context, appProvider, child) => SwitchListTile(
secondary: Icon(Icons.content_cut),
title: Text(AppLocalizations.of(context)!.trimSilenceInVoiceMessages),
title: Text(
AppLocalizations.of(context)!.trimSilenceInVoiceMessages,
),
subtitle: const Text(
'Removes long silent parts before sending voice',
),
@@ -1648,7 +1774,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
ListTile(
leading: Icon(Icons.timer),
title: Text(AppLocalizations.of(context)!.activeuseUpdateInterval),
title: Text(
AppLocalizations.of(context)!.activeuseUpdateInterval,
),
subtitle: Text('$_fastLocationActiveCadenceSeconds s'),
trailing: const Icon(Icons.chevron_right),
onTap: _editFastLocationActiveCadence,