diff --git a/lib/controllers/settings_sections.dart b/lib/controllers/settings_sections.dart index 073019a..e00778e 100644 --- a/lib/controllers/settings_sections.dart +++ b/lib/controllers/settings_sections.dart @@ -15,6 +15,8 @@ import "package:nexus/controllers/settings.dart"; import "package:nexus/controllers/unified_push.dart"; import "package:nexus/controllers/spec_versions.dart"; import "package:nexus/controllers/unified_push_allowed.dart"; +import "package:nexus/controllers/unified_push_distributors.dart"; +import "package:nexus/helpers/extensions/distributor_name.dart"; import "package:nexus/models/account_data.dart"; import "package:nexus/models/settings_category.dart"; import "package:nexus/main.dart"; @@ -138,7 +140,7 @@ class SettingsSectionsController settings: .new([ .new( title: "Push notifications", - description: "Enable push notifications using Web Push", + description: "Enable push notifications using Web Push, and choose which UnifiedPush distributor to use.", builder: (title, description, icon) => HookConsumer( builder: (context, ref, _) { final loading = useState(false); @@ -148,9 +150,14 @@ class SettingsSectionsController final unifiedPushAllowed = ref.watch( UnifiedPushAllowedController.provider, ); + final distributors = ref.watch( + UnifiedPushDistributorsController.provider, + ); + final current = pusherRegistered.value; - return SwitchListTile( - title: Text(title), + return DialogListTile( + key: ValueKey(current), + title: title, subtitle: Text( unifiedPushAllowed.maybeWhen( data: (data) => data, @@ -158,11 +165,18 @@ class SettingsSectionsController ) ?? description, ), - secondary: Icon(icon), - value: pusherRegistered.maybeWhen( - data: (value) => value, - orElse: () => false, - ), + icon: Icon(icon), + required: false, + initialValue: current, + options: [ + null, + ...?distributors.value, + if (current != null && + distributors.value?.contains(current) != true) + current, + ], + getName: (option) => + option == null ? "Off" : option.distributorName, onChanged: loading.value ? null : unifiedPushAllowed.maybeWhen( @@ -171,9 +185,10 @@ class SettingsSectionsController ) ? pusherRegistered.maybeWhen( data: (_) => (value) async { + if (value == current) return; try { loading.value = true; - if (value) { + if (value != null) { if (await ref .watch( NotificationController @@ -187,7 +202,7 @@ class SettingsSectionsController .provider .notifier, ) - .register(); + .register(value); } else { // TODO: Handle not granted } diff --git a/lib/controllers/unified_push.dart b/lib/controllers/unified_push.dart index 97849fe..638e330 100644 --- a/lib/controllers/unified_push.dart +++ b/lib/controllers/unified_push.dart @@ -17,12 +17,12 @@ import "package:unifiedpush/unifiedpush.dart"; import "package:unifiedpush_storage_shared_preferences/storage.dart"; import "package:window_manager/window_manager.dart"; -class UnifiedPushController extends AsyncNotifier { +class UnifiedPushController extends AsyncNotifier { Completer? _endpoint; @override - Future build() async { - if (!Platform.isLinux && !Platform.isAndroid) return false; + Future build() async { + if (!Platform.isLinux && !Platform.isAndroid) return null; final registered = await UnifiedPush.initialize( linuxOptions: .new( @@ -56,7 +56,7 @@ class UnifiedPushController extends AsyncNotifier { ), ); - state = .data(true); + state = .data(await UnifiedPush.getDistributor()); }); if (_endpoint case final completer? when !completer.isCompleted) { @@ -125,17 +125,25 @@ class UnifiedPushController extends AsyncNotifier { onUnregistered: deregister, ); - if (!registered) return false; + if (!registered) return null; try { return await register(); } catch (error, stackTrace) { showError(error, stackTrace); - return false; + return null; } } - Future register() async { + /// Registers with [distributor], or the current/default distributor if none + /// is given. Returns the distributor that was registered with. + Future register([String? distributor]) async { + if (distributor != null && + state.value != null && + state.value != distributor) { + await deregister(); + } + state = .loading(); try { final deviceIdCompleter = Completer(); @@ -163,7 +171,9 @@ class UnifiedPushController extends AsyncNotifier { ); } - if (!await UnifiedPush.tryUseCurrentOrDefaultDistributor()) { + if (distributor != null) { + await UnifiedPush.saveDistributor(distributor); + } else if (!await UnifiedPush.tryUseCurrentOrDefaultDistributor()) { throw Exception("No UnifiedPush distributors found."); } @@ -185,10 +195,11 @@ class UnifiedPushController extends AsyncNotifier { onTimeout: () => throw Exception("UnifiedPush registration timed out."), ); - state = .data(true); - return true; + final current = await UnifiedPush.getDistributor(); + state = .data(current); + return current; } catch (_) { - state = .data(false); + state = .data(null); rethrow; } } @@ -213,10 +224,10 @@ class UnifiedPushController extends AsyncNotifier { } await UnifiedPush.unregister(instance ?? clientState!.deviceId!); - state = .data(false); + state = .data(null); } - static final provider = AsyncNotifierProvider( + static final provider = AsyncNotifierProvider( UnifiedPushController.new, ); } diff --git a/lib/controllers/unified_push_allowed.dart b/lib/controllers/unified_push_allowed.dart index 604a060..515239a 100644 --- a/lib/controllers/unified_push_allowed.dart +++ b/lib/controllers/unified_push_allowed.dart @@ -2,16 +2,16 @@ import "dart:io"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client.dart"; -import "package:unifiedpush/unifiedpush.dart"; +import "package:nexus/controllers/unified_push_distributors.dart"; class UnifiedPushAllowedController extends AsyncNotifier { @override Future build() async { - if (!await UnifiedPush.tryUseCurrentOrDefaultDistributor().timeout( - .new(seconds: 10), - onTimeout: () => - throw Exception("UnifiedPush distributor check timed out."), - )) { + final distributors = await ref.watch( + UnifiedPushDistributorsController.provider.future, + ); + + if (distributors.isEmpty) { return "No valid distributors found. ${Platform.isLinux ? "Try installing KUnifiedPush" : Platform.isAndroid diff --git a/lib/controllers/unified_push_distributors.dart b/lib/controllers/unified_push_distributors.dart new file mode 100644 index 0000000..15fbec4 --- /dev/null +++ b/lib/controllers/unified_push_distributors.dart @@ -0,0 +1,26 @@ +import "dart:io"; + +import "package:fast_immutable_collections/fast_immutable_collections.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:unifiedpush/unifiedpush.dart"; + +class UnifiedPushDistributorsController extends AsyncNotifier> { + @override + Future> build() async { + if (!Platform.isLinux && !Platform.isAndroid) return .new(); + + return IList( + await UnifiedPush.getDistributors().timeout( + .new(seconds: 10), + onTimeout: () => + throw Exception("UnifiedPush distributor check timed out."), + ), + ); + } + + static final provider = + AsyncNotifierProvider.autoDispose< + UnifiedPushDistributorsController, + IList + >(UnifiedPushDistributorsController.new); +} diff --git a/lib/helpers/extensions/distributor_name.dart b/lib/helpers/extensions/distributor_name.dart new file mode 100644 index 0000000..07c1be6 --- /dev/null +++ b/lib/helpers/extensions/distributor_name.dart @@ -0,0 +1,5 @@ +extension DistributorName on String { + String get distributorName => startsWith("nexus.federated.nexus") + ? "Google Play Services" + : split(".").last; +}