diff --git a/lib/controllers/client.dart b/lib/controllers/client.dart index 20a3954..60ab6ce 100644 --- a/lib/controllers/client.dart +++ b/lib/controllers/client.dart @@ -22,6 +22,7 @@ import "package:nexus/models/event.dart"; import "package:nexus/models/oauth_auth_code_response.dart"; import "package:nexus/models/open_graph_data.dart"; import "package:nexus/models/paginate.dart"; +import "package:nexus/models/requests/deregister_pusher.dart"; import "package:nexus/models/requests/download_media.dart"; import "package:nexus/models/requests/get_event.dart"; import "package:nexus/models/requests/get_related_events.dart"; @@ -178,12 +179,14 @@ class ClientController extends AsyncNotifier { return json; } - Future handlePush(Map data) async => .fromJson( - await callGomuksMethod( + Future handlePush(Map data) async { + final response = await callGomuksMethod( data, (handle, data) async => GomuksHandlePush(handle, data), - ), - ); + ); + + return response == null ? null : .fromJson(response); + } Future _sendCommand( String command, [ @@ -293,6 +296,12 @@ class ClientController extends AsyncNotifier { Future registerPusher(RegisterPusherRequest request) => _sendCommand("register_homeserver_push", request.toJson()); + Future deregisterPusher(DeregisterPusherRequest request) => + _sendCommand("register_homeserver_push", { + ...request.toJson(), + "kind": null, + }); + Future uploadMedia(UploadMediaRequest request) async => .fromJson(await _sendCommand("upload_media", request.toJson())); diff --git a/lib/controllers/notifications.dart b/lib/controllers/notifications.dart index d9bcd8f..bd80467 100644 --- a/lib/controllers/notifications.dart +++ b/lib/controllers/notifications.dart @@ -1,11 +1,12 @@ -import "dart:convert"; import "dart:io"; +import "package:material_ui/material_ui.dart"; +import "package:nexus/main.dart"; import "package:flutter_local_notifications/flutter_local_notifications.dart"; // ignore: implementation_imports import "package:flutter_local_notifications_linux/src/model/hint.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/event.dart"; +import "package:nexus/pages/notifications.dart"; class NotificationsController extends AsyncNotifier { @@ -22,16 +23,20 @@ class NotificationsController appUserModelId: "nexus.federated.nexus", guid: "dde78daf-130f-4e46-a80a-e31deeab45d7", ), - android: .new("ic_launcher"), + android: .new("ic_launcher_foreground"), iOS: darwin, macOS: darwin, linux: .new(defaultActionName: "Open"), ), onDidReceiveNotificationResponse: (details) { - if (details.payload case final payload?) { - final event = ref.watch( - EventController.provider(.fromJson(json.decode(payload))), - ); + if (details.payload case final eventId?) { + if (navigatorKey.currentContext case final context?) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => NotificationsPage(eventId: eventId), + ), + ); + } } }, ); diff --git a/lib/controllers/settings_sections.dart b/lib/controllers/settings_sections.dart index 35753fe..1e60263 100644 --- a/lib/controllers/settings_sections.dart +++ b/lib/controllers/settings_sections.dart @@ -1,8 +1,9 @@ import "dart:io"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:material_ui/material_ui.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:intl/intl.dart"; import "package:m3e_buttons/m3e_buttons.dart"; import "package:nexus/controllers/account_data.dart"; @@ -105,14 +106,16 @@ class SettingsSectionsController .new( title: "Push notifications", description: "Enable push notifications using Web Push", - builder: (title, description, icon) => Consumer( + builder: (title, description, icon) => HookConsumer( builder: (context, ref, _) { + final loading = useState(false); final pusherRegistered = ref.watch( UnifiedPushController.provider, ); final unifiedPushAllowed = ref.watch( UnifiedPushAllowedController.provider, ); + return SwitchListTile( title: Text(title), subtitle: Text( @@ -127,34 +130,47 @@ class SettingsSectionsController data: (value) => value, orElse: () => false, ), - onChanged: - unifiedPushAllowed.maybeWhen( - data: (data) => data == null, - orElse: () => false, - ) + onChanged: loading.value + ? null + : unifiedPushAllowed.maybeWhen( + data: (data) => data == null, + orElse: () => false, + ) ? pusherRegistered.maybeWhen( data: (_) => (value) async { - if (value) { - if (await ref - .watch( - NotificationsController - .provider - .notifier, - ) - .requestPermissions()) { - ref + try { + loading.value = true; + if (value) { + if (await ref + .watch( + NotificationsController + .provider + .notifier, + ) + .requestPermissions()) { + await ref + .watch( + UnifiedPushController + .provider + .notifier, + ) + .register(); + } else { + // TODO: Handle not granted + } + } else { + await ref .watch( UnifiedPushController .provider .notifier, ) - .register() - .onError(showError); - } else { - // TODO: Handle not granted + .deregister(); } - } else { - // TODO: Deregister + } catch (error, stackTrace) { + showError(error, stackTrace); + } finally { + loading.value = false; } }, orElse: () => null, diff --git a/lib/controllers/unified_push.dart b/lib/controllers/unified_push.dart index 1e90114..f9d8f8f 100644 --- a/lib/controllers/unified_push.dart +++ b/lib/controllers/unified_push.dart @@ -11,7 +11,6 @@ import "package:nexus/controllers/client.dart"; import "package:nexus/controllers/client_state.dart"; import "package:nexus/models/content/message.dart"; import "package:nexus/models/content/sticker.dart"; -import "package:nexus/models/requests/get_event.dart"; import "package:nexus/models/requests/register_pusher.dart"; import "package:unifiedpush/unifiedpush.dart"; import "package:unifiedpush_storage_shared_preferences/storage.dart"; @@ -52,9 +51,10 @@ class UnifiedPushController extends AsyncNotifier { json.decode(String.fromCharCodes(message.content)), ); - if (event.unreadType?.shouldNotify != true || + if (event == null || + event.unreadType?.shouldNotify != true || (!isInBackground && - await windowManager.isFocused() && + await windowManager.isFocused().onError((_, _) => false) && ref.watch(KeyController.provider(KeyController.roomKey)) == event.roomId)) { return; @@ -77,9 +77,7 @@ class UnifiedPushController extends AsyncNotifier { id: event.eventId.hashCode & 0x7fffffff, title: room?.metadata?.name ?? "New Event", icon: icon, - payload: json.encode( - GetEventRequest(eventId: event.eventId, roomId: event.roomId), - ), + payload: event.eventId, body: switch (event.content) { MessageContent(:final body?) || StickerContent(:final body) => body, @@ -128,6 +126,24 @@ class UnifiedPushController extends AsyncNotifier { if (!alreadyRegistered) ref.invalidateSelf(); } + Future deregister() async { + final clientState = ref.watch(ClientStateController.provider); + final key = await UnifiedPushStorageSharedPreferences().keys.get( + clientState!.deviceId!, + ); + + if (key == null) return; + + await ref + .watch(ClientController.provider.notifier) + .deregisterPusher( + .new(appId: "nexus.federated.nexus", pushKey: key.split("=").first), + ); + + await UnifiedPush.unregister(clientState.deviceId!); + ref.invalidateSelf(); + } + static final provider = AsyncNotifierProvider( UnifiedPushController.new, ); diff --git a/lib/models/requests/deregister_pusher.dart b/lib/models/requests/deregister_pusher.dart new file mode 100644 index 0000000..1416add --- /dev/null +++ b/lib/models/requests/deregister_pusher.dart @@ -0,0 +1,13 @@ +import "package:freezed_annotation/freezed_annotation.dart"; + +part "deregister_pusher.freezed.dart"; +part "deregister_pusher.g.dart"; + +@Freezed(toJson: false, fromJson: false) +@JsonSerializable() +class const DeregisterPusherRequest({ + required final String appId, + @JsonKey(name: "pushkey") required final String pushKey, +}) with _$DeregisterPusherRequest { + Map toJson() => _$DeregisterPusherRequestToJson(this); +} diff --git a/lib/pages/notifications.dart b/lib/pages/notifications.dart new file mode 100644 index 0000000..ceca514 --- /dev/null +++ b/lib/pages/notifications.dart @@ -0,0 +1,14 @@ +import "package:material_ui/material_ui.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:nexus/widgets/appbar.dart"; + +class const NotificationsPage({final String? eventId, super.key}) + extends HookConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: Appbar(title: Text("Notifications")), + body: Placeholder(), + ); + } +} diff --git a/lib/widgets/appbar.dart b/lib/widgets/appbar.dart index a6058ea..33727ac 100644 --- a/lib/widgets/appbar.dart +++ b/lib/widgets/appbar.dart @@ -39,7 +39,7 @@ final class const Appbar({ : (_) => windowManager.startDragging(), ), child: AppBar( - leading: InkWell(onTap: onTap, child: leading), + leading: leading == null ? null : InkWell(onTap: onTap, child: leading), backgroundColor: backgroundColor, scrolledUnderElevation: scrolledUnderElevation, actionsPadding: const .symmetric(horizontal: 8),