Skip to content

Comments

Unread badges for tabs; "People first" contacts sorting option#145

Draft
pioneer wants to merge 7 commits intozjs81:mainfrom
pioneer:unread-peoplefirst
Draft

Unread badges for tabs; "People first" contacts sorting option#145
pioneer wants to merge 7 commits intozjs81:mainfrom
pioneer:unread-peoplefirst

Conversation

@pioneer
Copy link

@pioneer pioneer commented Feb 9, 2026

Bottom tabs (Contacts, Channels) didn't show whether there are new messages in channels or in direct messages. Now, when any channel has a new message, the Channels tab receives an unread badge with the total count of unread messages in all channels. Same for Contacts.

Added "Users first" option in Contacts so that people will be on top of the list, leaving repeaters and the rest below them.

Added Ukrainian translation for "Users first", and translation stubs for all other languages.

Closes #91

Copilot AI review requested due to automatic review settings February 9, 2026 14:34
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds unread-count badges to the bottom navigation tabs (Contacts/Channels) and introduces a new Contacts list ordering toggle to keep people (users) at the top, with localization updates to support the new UI label.

Changes:

  • Add unread badge rendering to QuickSwitchBar and wire unread totals from MeshCoreConnector into screens using the bottom navigation.
  • Add a “users/people first” toggle to the Contacts filter menu and update contacts sorting to partition users before other contact types.
  • Add the new localization key + Ukrainian translation, and update untranslated.json plus generated localization outputs.

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
untranslated.json Updates the untranslated-keys list to reflect the new listFilter_usersFirst string.
lib/widgets/quick_switch_bar.dart Adds badge display to Contacts/Channels icons and adds selected icon for Map.
lib/widgets/list_filter_widget.dart Adds “users first” toggle option to the Contacts filter popup menu.
lib/screens/map_screen.dart Passes unread totals into QuickSwitchBar.
lib/screens/device_screen.dart Passes unread totals into QuickSwitchBar from the device hub screen.
lib/screens/contacts_screen.dart Introduces _prioritizePeople and applies people-first ordering + refactors sorting into a helper.
lib/screens/channels_screen.dart Passes unread totals into QuickSwitchBar.
lib/connector/meshcore_connector.dart Splits unread total counting into contacts vs channels for badge usage.
lib/l10n/app_en.arb Adds the new listFilter_usersFirst string.
lib/l10n/app_uk.arb Adds Ukrainian translation for listFilter_usersFirst and updates a few other Ukrainian strings.
lib/l10n/app_localizations.dart Adds the new listFilter_usersFirst abstract getter (generated).
lib/l10n/app_localizations_en.dart Adds listFilter_usersFirst getter (generated).
lib/l10n/app_localizations_bg.dart Adds listFilter_usersFirst getter (generated).
lib/l10n/app_localizations_de.dart Adds listFilter_usersFirst getter (generated).
lib/l10n/app_localizations_es.dart Adds listFilter_usersFirst getter (generated).
lib/l10n/app_localizations_fr.dart Adds listFilter_usersFirst getter (generated).
lib/l10n/app_localizations_it.dart Adds listFilter_usersFirst getter (generated).
lib/l10n/app_localizations_nl.dart Adds listFilter_usersFirst getter (generated).
lib/l10n/app_localizations_pl.dart Adds listFilter_usersFirst getter (generated).
lib/l10n/app_localizations_pt.dart Adds listFilter_usersFirst getter (generated).
lib/l10n/app_localizations_ru.dart Adds listFilter_usersFirst getter (generated).
lib/l10n/app_localizations_sk.dart Adds listFilter_usersFirst getter (generated).
lib/l10n/app_localizations_sl.dart Adds listFilter_usersFirst getter (generated).
lib/l10n/app_localizations_sv.dart Adds listFilter_usersFirst getter (generated).
lib/l10n/app_localizations_uk.dart Adds listFilter_usersFirst getter + Ukrainian string updates (generated).
lib/l10n/app_localizations_zh.dart Adds listFilter_usersFirst getter (generated).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings February 9, 2026 15:34
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 418 to 422
final previousCount = _contactUnreadCount[contactKeyHex] ?? 0;
if (previousCount > 0) {
_contactUnreadCount[contactKeyHex] = 0;
_cachedContactsUnreadTotal -= previousCount;
_appDebugLogService?.info(
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_cachedContactsUnreadTotal is decremented by previousCount when marking a contact read, but the cached total can become out-of-sync (e.g., when entries are removed from _contactUnreadCount elsewhere) and end up negative. It would be safer to clamp to >= 0 (or periodically recalculate) after applying the delta so UI badges don’t show incorrect totals.

Copilot uses AI. Check for mistakes.
Comment on lines 435 to 439
if (channel != null && channel.unreadCount > 0) {
final previousCount = channel.unreadCount;
channel.unreadCount = 0;
_cachedChannelsUnreadTotal -= previousCount;
_appDebugLogService?.info(
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_cachedChannelsUnreadTotal is decremented by previousCount when marking a channel read; if the cache ever becomes out-of-sync, this can drive the total negative. Consider clamping to >= 0 (or recalculating) after applying the delta to avoid incorrect badge counts.

Copilot uses AI. Check for mistakes.
Comment on lines 106 to 112

return Badge(
label: Text(
count > 99 ? '99+' : count.toString(),
style: const TextStyle(fontSize: 10),
),
child: icon,
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QuickSwitchBar introduces a second unread badge implementation using Material Badge, while the rest of the UI uses the custom UnreadBadge widget for unread counts. This is likely to create inconsistent styling/behavior (colors, text scaling, theming) across the app. Consider reusing UnreadBadge here (e.g., via a Stack/Positioned overlay) or centralizing badge rendering so unread indicators look and behave consistently.

Suggested change
return Badge(
label: Text(
count > 99 ? '99+' : count.toString(),
style: const TextStyle(fontSize: 10),
),
child: icon,
return UnreadBadge(
count: count,
child: icon,
);
}
}
class UnreadBadge extends StatelessWidget {
final int count;
final Widget child;
const UnreadBadge({
super.key,
required this.count,
required this.child,
});
@override
Widget build(BuildContext context) {
if (count <= 0) return child;
final displayText = count > 99 ? '99+' : count.toString();
return Badge(
label: Text(
displayText,
style: const TextStyle(fontSize: 10),
),
child: child,

Copilot uses AI. Check for mistakes.
}

void _recalculateCachedContactsUnreadTotal() {
_cachedContactsUnreadTotal = _contactUnreadCount.values.fold(0, (a, b) => a + b);
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_recalculateCachedContactsUnreadTotal() sums all values in _contactUnreadCount, which can include repeaters or other contacts that are intentionally excluded by getUnreadCountForContact/getUnreadCountForContactKey (e.g., advTypeRepeater). This can inflate the Contacts tab badge/notification counts compared to the previous behavior. Consider recalculating the cached total using the same filtering logic as getTotalUnreadCount previously did (sum over non-repeater contacts / _shouldTrackUnreadForContactKey), and ensure the cache stays consistent when contacts change type (e.g., when a contact is refreshed as a repeater).

Suggested change
_cachedContactsUnreadTotal = _contactUnreadCount.values.fold(0, (a, b) => a + b);
int total = 0;
_contactUnreadCount.forEach((contactKeyHex, count) {
if (_shouldTrackUnreadForContactKey(contactKeyHex)) {
total += count;
}
});
_cachedContactsUnreadTotal = total;

Copilot uses AI. Check for mistakes.
@446564
Copy link
Collaborator

446564 commented Feb 9, 2026

I'll take a look later today.

@pioneer pioneer marked this pull request as draft February 10, 2026 09:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Bring DM's to top of node list

2 participants