diff --git a/mobile/lib/features/settings/settings_page/notifications_section.dart b/mobile/lib/features/settings/settings_page/notifications_section.dart index 4e314dc38a..ab807ea92b 100644 --- a/mobile/lib/features/settings/settings_page/notifications_section.dart +++ b/mobile/lib/features/settings/settings_page/notifications_section.dart @@ -25,9 +25,9 @@ class _NotificationsSection extends ConsumerWidget { return const SizedBox.shrink(); } final canToggle = hasCapability || community.pushNotificationsEnabled; - final overflow = community.pushNotificationsEnabled - ? ref.watch(pushSubscriptionSyncProvider).value - : null; + final omittedSoloChats = community.pushNotificationsEnabled + ? ref.watch(pushSubscriptionSyncProvider).value ?? 0 + : 0; void setEnabled(bool enabled) { unawaited( @@ -75,26 +75,15 @@ class _NotificationsSection extends ConsumerWidget { ], ), ), - if (overflow != null && overflow.chats > 0) + if (omittedSoloChats > 0) AppListRow( key: const ValueKey('push-solo-coverage'), title: 'Older Solo chats notify only on mention', - subtitle: _soloOverflowText(overflow), + subtitle: omittedSoloChats == 1 + ? '1 chat is past this relay’s notification limit.' + : '$omittedSoloChats chats are past this relay’s notification limit.', ), ], ); } } - -String _soloOverflowText(SoloOverflow overflow) { - final past = overflow.chats == 1 - ? '1 chat is past this relay’s notification limit.' - : '${overflow.chats} chats are past this relay’s notification limit.'; - if (overflow.muted == 0) return past; - if (overflow.chats == 1) { - return '$past It is muted but still notifies on mention.'; - } - return overflow.muted == 1 - ? '$past 1 of them is muted but still notifies on mention.' - : '$past ${overflow.muted} of them are muted but still notify on mention.'; -} diff --git a/mobile/lib/shared/push/push_subscription_provider.dart b/mobile/lib/shared/push/push_subscription_provider.dart index dcb662b382..c94953e218 100644 --- a/mobile/lib/shared/push/push_subscription_provider.dart +++ b/mobile/lib/shared/push/push_subscription_provider.dart @@ -15,25 +15,25 @@ import 'push_setup_recovery.dart'; /// the active identity, joined channels, and mute state. This is desired client /// policy only. Relay-accepted authority is introduced by lease publication. /// -/// Yields the Solo chats the lease had no room for. -final pushSubscriptionSyncProvider = FutureProvider.autoDispose(( +/// Yields how many Solo chats the lease had no room for. +final pushSubscriptionSyncProvider = FutureProvider.autoDispose(( ref, ) async { try { if (ref.watch(authProvider).value?.status != AuthStatus.authenticated) { - return _noOverflow; + return 0; } final active = ref.watch(activeCommunityProvider).value; final descriptor = ref.watch(currentRelayPushDescriptorProvider).value; if (active == null || !active.pushNotificationsEnabled || descriptor == null) { - return _noOverflow; + return 0; } final channels = ref.watch(channelsProvider).value; final mutes = ref.watch(channelMutesProvider); if (channels == null || !mutes.isReady) { - return _noOverflow; + return 0; } // Legacy Solo chats are listed only in the Solo record. Wait while it @@ -41,7 +41,7 @@ final pushSubscriptionSyncProvider = FutureProvider.autoDispose(( // cached copy, keep DMs and mentions current; the Solo tab cannot list // those chats either, and recovery republishes them. final soloRecord = ref.watch(meGroupsProvider); - if (soloRecord.isLoading && !soloRecord.hasValue) return _noOverflow; + if (soloRecord.isLoading && !soloRecord.hasValue) return 0; final desired = desiredBuzzPushSubscriptions( community: active, @@ -53,25 +53,19 @@ final pushSubscriptionSyncProvider = FutureProvider.autoDispose(( if (entry.value.muted) entry.key, ], ); - if (desired == null) return _noOverflow; + if (desired == null) return 0; await ref .read(communityListProvider.notifier) .updateDesiredPushSubscriptions(active.id, desired.subscriptions); - return desired.overflow; + return desired.omittedSoloChats; } catch (error, stack) { reportPushSetupError('subscription synchronization', error, stack); rethrow; } }, retry: pushSetupRetry); -/// Solo chats a lease had no room for, and how many of those are muted. -/// Mentions in a muted one still notify, since its mute ignore did not fit. -typedef SoloOverflow = ({int chats, int muted}); - -const SoloOverflow _noOverflow = (chats: 0, muted: 0); - -/// The desired lease, and the Solo chats it had no room for. -({List subscriptions, SoloOverflow overflow})? +/// The desired lease, and how many Solo chats it had no room for. +({List subscriptions, int omittedSoloChats})? desiredBuzzPushSubscriptions({ required Community community, required BuzzPushLeaseDescriptor descriptor, @@ -118,15 +112,9 @@ desiredBuzzPushSubscriptions({ } fitting = low; } - final trimmed = candidates.solos.skip(fitting); return ( subscriptions: lease(fitting), - overflow: ( - chats: candidates.omitted + trimmed.length, - muted: - candidates.omittedMuted + - trimmed.where((id) => muted.contains(id.toLowerCase())).length, - ), + omittedSoloChats: candidates.omitted + candidates.solos.length - fitting, ); } @@ -136,8 +124,7 @@ desiredBuzzPushSubscriptions({ /// /// DMs always stay, as before. Solo chats follow most recent first, within the /// lease's channel counts; the rest push only on mention. -({List dms, List solos, int omitted, int omittedMuted}) -_channelWideCandidates({ +({List dms, List solos, int omitted}) _channelWideCandidates({ required String pubkey, required Iterable channels, required Set muted, @@ -162,22 +149,13 @@ _channelWideCandidates({ buzzPushMaxChannelWideChannels - dms.where((id) => !isMuted(id)).length; var mutedRoom = buzzPushMaxMutedChannels - dms.where(isMuted).length; final solos = []; - var omittedMuted = 0; for (final solo in ordered) { final soloMuted = isMuted(solo.id); - if ((soloMuted ? mutedRoom : unmutedRoom) <= 0) { - if (soloMuted) omittedMuted++; - continue; - } + if ((soloMuted ? mutedRoom : unmutedRoom) <= 0) continue; solos.add(solo.id); soloMuted ? mutedRoom-- : unmutedRoom--; } - return ( - dms: dms, - solos: solos, - omitted: ordered.length - solos.length, - omittedMuted: omittedMuted, - ); + return (dms: dms, solos: solos, omitted: ordered.length - solos.length); } int _mostRecentFirst(Channel a, Channel b) { diff --git a/mobile/test/features/settings/settings_page_test.dart b/mobile/test/features/settings/settings_page_test.dart index 336984ca91..1d29e35d93 100644 --- a/mobile/test/features/settings/settings_page_test.dart +++ b/mobile/test/features/settings/settings_page_test.dart @@ -171,10 +171,8 @@ void main() { savedPrefsProvider.overrideWithValue(prefs), activeCommunityProvider.overrideWith((ref) async => community), currentRelayPushDescriptorProvider.overrideWith((ref) => capability), - // The lease had no room for 12 older Solo chats, 3 of them muted. - pushSubscriptionSyncProvider.overrideWith( - (ref) async => (chats: 12, muted: 3), - ), + // The lease had no room for 12 older Solo chats. + pushSubscriptionSyncProvider.overrideWith((ref) async => 12), appLifecycleProvider.overrideWith(_SettingsLifecycleNotifier.new), buzzPushAuthorizationStatusReaderProvider.overrideWithValue( () async => BuzzPushAuthorizationStatus.authorized, @@ -201,10 +199,7 @@ void main() { findsOneWidget, ); expect( - find.text( - '12 chats are past this relay’s notification limit. ' - '3 of them are muted but still notify on mention.', - ), + find.text('12 chats are past this relay’s notification limit.'), findsOneWidget, ); debugDefaultTargetPlatformOverride = null; diff --git a/mobile/test/shared/push/push_subscription_provider_test.dart b/mobile/test/shared/push/push_subscription_provider_test.dart index a7b8080aec..92b0367f78 100644 --- a/mobile/test/shared/push/push_subscription_provider_test.dart +++ b/mobile/test/shared/push/push_subscription_provider_test.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:math' as math; import 'package:buzz/features/channels/channel.dart'; import 'package:buzz/features/channels/channel_mutes/channel_mutes_provider.dart'; @@ -119,7 +118,7 @@ void main() { expect(followed, containsAll([for (var n = 0; n < dmCount; n++) _id(n)])); expect(followed, contains(_id(channels.length - 1))); expect(followed, isNot(contains(_id(dmCount + dmCount - 1)))); - expect(desired.overflow, (chats: dmCount, muted: 0)); + expect(desired.omittedSoloChats, dmCount); // A relay may advertise any endpoint limit; sizing must not allocate it. expect( @@ -146,12 +145,6 @@ void main() { 400, 100, ), - ( - 'content, trimming muted chats,', - _descriptor(contentLength: 24000, plaintextLength: 32768), - 400, - 300, - ), ('muted channel count', _descriptor(), 400, 400), ('NIP-44 plaintext', _descriptor(), 600, 200), ]) { @@ -182,10 +175,8 @@ void main() { for (final ignore in desired.subscriptions.first.ignore.skip(1)) ...?ignore.hTags, }; - final keptSolos = soloCount - desired.overflow.chats; + final keptSolos = soloCount - desired.omittedSoloChats; expect(keptSolos, inInclusiveRange(1, soloCount - 1)); - // Omitted muted chats lose their mute ignore, so Settings counts them. - expect(desired.overflow.muted, math.max(0, mutedSolos - keptSolos)); expect(kept, { for (var n = 0; n < dmCount; n++) _id(n), for (var n = channels.length - keptSolos; n < channels.length; n++)