implemented a guided notification permission flow triggered on the first Talk visit

This commit is contained in:
2026-07-06 20:01:45 +02:00
parent 3be0113f93
commit 38a271929c
11 changed files with 211 additions and 30 deletions
+21 -5
View File
@@ -333,10 +333,26 @@ class PushRegistration {
}
}
/// True when the OS notification permission is already granted
/// (`authorized`/`provisional`). Read-only — never triggers the OS prompt.
/// Used by the cold-start/self-heal path so it registers only for devices
/// that already opted in, leaving the actual prompt to the first Talk visit.
static Future<bool> isOsPermissionGranted() async {
try {
final settings = await FirebaseMessaging.instance
.getNotificationSettings();
return settings.authorizationStatus == AuthorizationStatus.authorized ||
settings.authorizationStatus == AuthorizationStatus.provisional;
} on Object {
return false;
}
}
/// Registers this device when push is both user-enabled and backend-capable.
/// Requests the OS notification permission first (covers iOS + Android 13);
/// an explicit denial skips registration entirely so NC/proxy never push to a
/// device that cannot display notifications. Safe to call on every start —
/// Only registers when the OS notification permission is *already* granted —
/// it never triggers the OS prompt itself. Requesting the permission is the
/// job of the first Talk visit (see `maybePromptTalkNotifications`), which
/// keeps the prompt out of the cold-start path. Safe to call on every start —
/// Nextcloud dedups an unchanged registration — which also self-heals a
/// device whose registration was lost.
static Future<void> syncSubscription({
@@ -344,8 +360,8 @@ class PushRegistration {
required bool capable,
}) async {
if (!(enabled && capable)) return;
if (!await requestOsPermission()) {
log('Push: OS notification permission denied, skipping registration');
if (!await isOsPermissionGranted()) {
log('Push: OS notification permission not granted, skipping registration');
return;
}
final registration = PushRegistration();
+11 -1
View File
@@ -160,7 +160,16 @@ class PushStatusRow {
/// for informational details (e.g. the registered URL).
final String? detail;
const PushStatusRow({required this.label, required this.state, this.detail});
/// When true, the row offers a shortcut into the OS notification settings —
/// the only place the user can (re)grant the permission after a denial.
final bool opensNotificationSettings;
const PushStatusRow({
required this.label,
required this.state,
this.detail,
this.opensNotificationSettings = false,
});
}
const _pendingDetail =
@@ -181,6 +190,7 @@ List<PushStatusRow> buildPushStatusRows(PushStatusReport r) => [
PushStatusRow(
label: 'Benachrichtigungsberechtigung',
state: r.osPermission,
opensNotificationSettings: true,
detail: switch (r.osPermission) {
PushCheck.ok => null,
PushCheck.fail =>