Files
buzz/mobile/lib/features/settings/settings_page/notifications_section.dart
Tom Brow fad4ff637a fix(mobile): hide notification settings without relay push capability (#8025)
Hide the iOS Notifications settings section for communities that have
not enabled push unless their relay advertises valid push support. A
build with a configured gateway previously showed the switch even when
the relay could not support push.

Communities where push is already enabled retain an off switch and iOS
Settings recovery during discovery or when push support is unavailable.
The section explains that support is unavailable instead of claiming
notifications will arrive.

Capability discovery keeps its existing refresh behavior. Returning
after at least five seconds in the background reconnects the session and
fetches capability again. Tom explicitly chose this behavior without new
retries, polling, or refresh triggers.

Companion client change for squareup/buzz-releases#104. Related: #7826
(broader notification availability diagnostics).

### Validation

Settings widget tests cover hidden first-time opt-in during loading,
absent capability, and discovery errors. Recovery tests exercise the
real off switch for already-enabled communities in all three states,
verifying persisted opt-out, the native snapshot writer, a durable
pending relay tombstone after failure, and the iOS Settings link. They
also verify that pending recovery stays visible after a relay failure
and disappears only after opt-out confirmation. Tests without a gateway
definition verify hidden first-time settings and disabled native
registration. Mobile analysis and the mobile test suite passed.

---------

Signed-off-by: Tom Brow <tomb@block.xyz>
2026-10-01 11:08:12 -07:00

102 lines
3.8 KiB
Dart

part of '../settings_page.dart';
class _NotificationsSection extends ConsumerWidget {
const _NotificationsSection();
@override
Widget build(BuildContext context, WidgetRef ref) {
if (defaultTargetPlatform != TargetPlatform.iOS) {
return const SizedBox.shrink();
}
final community = ref.watch(activeCommunityProvider).value;
if (community == null) return const SizedBox.shrink();
final capability = ref.watch(currentRelayPushDescriptorProvider);
final hasCapability =
!capability.isLoading &&
!capability.hasError &&
capability.value != null;
final optOutPending =
community.pushSubscriptionState.pendingTombstoneGeneration != null;
if (!hasCapability &&
!community.pushNotificationsEnabled &&
!optOutPending) {
return const SizedBox.shrink();
}
final authorization = ref.watch(buzzPushAuthorizationStatusProvider);
final status = authorization.value;
final permissionUnavailable = authorization.hasError;
final permissionDenied = status == BuzzPushAuthorizationStatus.denied;
final showSettingsRecovery =
optOutPending ||
(community.pushNotificationsEnabled &&
(permissionDenied || permissionUnavailable));
final canToggle = hasCapability || community.pushNotificationsEnabled;
final subtitle = optOutPending
? 'Waiting for relay confirmation; notifications may continue'
: !community.pushNotificationsEnabled
? 'Off for this community'
: !hasCapability
? 'Push support unavailable; you can still turn notifications off'
: switch (status) {
BuzzPushAuthorizationStatus.notDetermined =>
'Waiting for iOS notification permission',
BuzzPushAuthorizationStatus.denied =>
'Enabled in Buzz, but disabled in iOS Settings',
BuzzPushAuthorizationStatus.authorized ||
BuzzPushAuthorizationStatus.provisional ||
BuzzPushAuthorizationStatus.ephemeral =>
'Receive message notifications from this community',
null when authorization.isLoading =>
'Checking iOS notification permission',
null => 'Enabled in Buzz; iOS permission status unavailable',
};
return AppListCard(
label: 'Notifications',
verticalPadding: Grid.twelve,
children: [
AppListRow(
key: const ValueKey('push-notifications-enabled'),
icon: LucideIcons.bell,
title: 'Push notifications',
subtitle: subtitle,
subtitleStyle: showSettingsRecovery
? context.textTheme.bodySmall?.copyWith(
color: context.colors.error,
)
: null,
trailing: Switch.adaptive(
value: community.pushNotificationsEnabled,
onChanged: !canToggle
? null
: (enabled) => unawaited(
ref
.read(communityListProvider.notifier)
.setPushNotificationsEnabled(community.id, enabled),
),
),
onTap: !canToggle
? null
: () => unawaited(
ref
.read(communityListProvider.notifier)
.setPushNotificationsEnabled(
community.id,
!community.pushNotificationsEnabled,
),
),
),
if (showSettingsRecovery)
AppListRow(
key: const ValueKey('push-notifications-open-settings'),
icon: LucideIcons.settings,
title: 'Open iOS Notification Settings',
onTap: () => unawaited(
ref.read(buzzPushNotificationSettingsOpenerProvider)(),
),
),
],
);
}
}