From 579c3a9fb06a4d321acb2f870dfe78fdb95c7d3b Mon Sep 17 00:00:00 2001 From: Tom Bursch Date: Sun, 20 Apr 2025 23:38:21 +0200 Subject: [PATCH 1/2] Draft unified push implementation --- kitchenowl/android/app/build.gradle | 9 + .../drawable-anydpi-v24/notification_icon.xml | 15 ++ .../res/drawable-hdpi/notification_icon.png | Bin 0 -> 320 bytes .../res/drawable-mdpi/notification_icon.png | Bin 0 -> 240 bytes .../res/drawable-xhdpi/notification_icon.png | Bin 0 -> 387 bytes .../res/drawable-xxhdpi/notification_icon.png | Bin 0 -> 526 bytes .../res/raw/com_tombursch_kitchenowl_keep.xml | 3 + kitchenowl/lib/app.dart | 3 + kitchenowl/lib/cubits/settings_cubit.dart | 18 ++ kitchenowl/lib/pages/settings_page.dart | 26 +++ .../lib/services/notification_service.dart | 169 ++++++++++++++++++ kitchenowl/pubspec.lock | 64 +++++++ kitchenowl/pubspec.yaml | 5 + .../windows/flutter/generated_plugins.cmake | 1 + 14 files changed, 313 insertions(+) create mode 100644 kitchenowl/android/app/src/main/res/drawable-anydpi-v24/notification_icon.xml create mode 100644 kitchenowl/android/app/src/main/res/drawable-hdpi/notification_icon.png create mode 100644 kitchenowl/android/app/src/main/res/drawable-mdpi/notification_icon.png create mode 100644 kitchenowl/android/app/src/main/res/drawable-xhdpi/notification_icon.png create mode 100644 kitchenowl/android/app/src/main/res/drawable-xxhdpi/notification_icon.png create mode 100644 kitchenowl/android/app/src/main/res/raw/com_tombursch_kitchenowl_keep.xml create mode 100644 kitchenowl/lib/services/notification_service.dart diff --git a/kitchenowl/android/app/build.gradle b/kitchenowl/android/app/build.gradle index ae9abee29..bff8fa34e 100644 --- a/kitchenowl/android/app/build.gradle +++ b/kitchenowl/android/app/build.gradle @@ -31,6 +31,13 @@ if (keystorePropertiesFile.exists()) { dependencies { implementation 'com.google.errorprone:error_prone_annotations:2.36.0' // required by flutter_secure_storage implementation 'com.github.spotbugs:spotbugs-annotations:4.8.6' // required by flutter_secure_storage + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' // required by local notifications + implementation 'androidx.window:window:1.0.0' // required by local notifications + implementation 'androidx.window:window-java:1.0.0' // required by local notifications +} + +configurations.configureEach { + exclude group: 'com.google.crypto.tink', module: 'tink' } android { @@ -39,6 +46,8 @@ android { ndkVersion flutter.ndkVersion compileOptions { + // Flag to enable support for the new language APIs (local notifications) + coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } diff --git a/kitchenowl/android/app/src/main/res/drawable-anydpi-v24/notification_icon.xml b/kitchenowl/android/app/src/main/res/drawable-anydpi-v24/notification_icon.xml new file mode 100644 index 000000000..0b082f05a --- /dev/null +++ b/kitchenowl/android/app/src/main/res/drawable-anydpi-v24/notification_icon.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/kitchenowl/android/app/src/main/res/drawable-hdpi/notification_icon.png b/kitchenowl/android/app/src/main/res/drawable-hdpi/notification_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5d4c0ffd32a96a80d539db16c5ebfb1c6a84de26 GIT binary patch literal 320 zcmV-G0l)r9Wr(3|FSj6(1)$rb<+Zo39_qJ{G*lp!SB*@E)qw}1CrE62@c;jRHitP5 zExTAIL>1{UWd7mb%;Le#u+dh8Tfy>?ts$Rc%E^EKXC&CVwk?iq;Ggies{Q{FX7(bT z3WlpSj2-F;h9yiPd@ScCPi{(3V9@%;==sKWnu3Xf`5Q;UO1W)nHa5od5+>Rau3DADEwnS;%)7{9%q1<-0P8foWQeg65l# nM~*X@blW)`6_IGOWneHo5tXs{c*|p;7Z^NU{an^LB{Ts5@Gepu literal 0 HcmV?d00001 diff --git a/kitchenowl/android/app/src/main/res/drawable-xhdpi/notification_icon.png b/kitchenowl/android/app/src/main/res/drawable-xhdpi/notification_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3e95dddad7a1e619076dd62eac501f28688a1f69 GIT binary patch literal 387 zcmV-}0et?6P)Nkl;~-!j=%=Gk&eIyx`A#$H=vqc6GIAO@eXY6P43?X-iHqlA=`7OlbY9k+aCN>-JObt}#g~h;lz^pA-PykG}FNw!j zX)kx8So@}=ks}=dKDi|(K>MY`=~H2$x=s!NpWGG`pm!K+cFgh5ZWPX3N+a15lW|9MMq!&{-cX0m1pUZ6>cf$Tjy1PV?RPVWH-p|)Wg#ce hQWpU6Edwicy#N8(=RAQ-D^36a002ovPDHLkV1mC#n)m+S5`iA@FqZoy2d z5lpf(`1T0Bc(AT{F^g3LpU%TK4nS%{^IiktbesPimJ{>QdMUU6szs8o5)u*N| zd1Bsr>-+Y-6N-1u6Zp2@a)K-8qy=vx-^=9$_X>pAT4QdVE!Hsq0L7{>b=K&h?+U<>k|?n~cl(9K#AWinureQMUM$3eS#(<(AU~ zt|@RW!a#>ar!a9J+Tqrp#h!U`G25472cZxKu2U0P6t5|+sTMof7TLSw%PdCDQvuhP zXQ%Q1TXInQbVsY00yI0nT>c9E*Z24C%#TEgfWBR4G9y>hyS=@l}%;4$j K=d#Wzp$P!NBj=|8 literal 0 HcmV?d00001 diff --git a/kitchenowl/android/app/src/main/res/raw/com_tombursch_kitchenowl_keep.xml b/kitchenowl/android/app/src/main/res/raw/com_tombursch_kitchenowl_keep.xml new file mode 100644 index 000000000..512c34250 --- /dev/null +++ b/kitchenowl/android/app/src/main/res/raw/com_tombursch_kitchenowl_keep.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/kitchenowl/lib/app.dart b/kitchenowl/lib/app.dart index 082cb9b09..145d0d0cf 100644 --- a/kitchenowl/lib/app.dart +++ b/kitchenowl/lib/app.dart @@ -17,6 +17,7 @@ import 'package:kitchenowl/kitchenowl.dart'; import 'package:kitchenowl/router.dart'; import 'package:kitchenowl/services/api/api_service.dart'; import 'package:kitchenowl/services/background_task.dart'; +import 'package:kitchenowl/services/notification_service.dart'; import 'package:kitchenowl/services/storage/storage.dart'; import 'package:kitchenowl/services/transaction_handler.dart'; import 'package:kitchenowl/styles/colors.dart'; @@ -109,6 +110,8 @@ class _AppState extends State { }, ); } + + NotificationService.getInstance().initialize(); } @override diff --git a/kitchenowl/lib/cubits/settings_cubit.dart b/kitchenowl/lib/cubits/settings_cubit.dart index 3b40b6a5e..d58f39639 100644 --- a/kitchenowl/lib/cubits/settings_cubit.dart +++ b/kitchenowl/lib/cubits/settings_cubit.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kitchenowl/config.dart'; import 'package:kitchenowl/kitchenowl.dart'; +import 'package:kitchenowl/services/notification_service.dart'; import 'package:kitchenowl/services/storage/storage.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -52,9 +53,19 @@ class SettingsCubit extends Cubit { shoppingListTapToRemove: await shoppingListTapToRemove ?? true, recentItemsCategorize: await recentItemsCategorize ?? false, restoreLastShoppingList: await restoreLastShoppingList ?? false, + notificationDistributor: + await NotificationService.getInstance().getDistributor(), )); } + Future refreshNotificationDistributor() async { + await Future.delayed(const Duration(seconds: 1)); + String? distributor = + await NotificationService.getInstance().getDistributor(); + print("Refresh: ${distributor}"); + emit(state.copyWith(notificationDistributor: Nullable(distributor))); + } + void setTheme(ThemeMode themeMode) { PreferenceStorage.getInstance() .writeInt(key: 'themeMode', value: themeMode.index); @@ -133,6 +144,7 @@ class SettingsState extends Equatable { final bool shoppingListTapToRemove; final bool recentItemsCategorize; final bool restoreLastShoppingList; + final String? notificationDistributor; const SettingsState({ this.themeMode = ThemeMode.system, @@ -144,6 +156,7 @@ class SettingsState extends Equatable { this.shoppingListTapToRemove = true, this.recentItemsCategorize = false, this.restoreLastShoppingList = false, + this.notificationDistributor, }); SettingsState copyWith({ @@ -156,6 +169,7 @@ class SettingsState extends Equatable { bool? shoppingListTapToRemove, bool? recentItemsCategorize, bool? restoreLastShoppingList, + Nullable? notificationDistributor, }) => SettingsState( themeMode: themeMode ?? this.themeMode, @@ -170,6 +184,9 @@ class SettingsState extends Equatable { recentItemsCategorize ?? this.recentItemsCategorize, restoreLastShoppingList: restoreLastShoppingList ?? this.restoreLastShoppingList, + notificationDistributor: + (notificationDistributor ?? Nullable(this.notificationDistributor)) + .value, ); @override @@ -183,5 +200,6 @@ class SettingsState extends Equatable { shoppingListTapToRemove, recentItemsCategorize, restoreLastShoppingList, + notificationDistributor ]; } diff --git a/kitchenowl/lib/pages/settings_page.dart b/kitchenowl/lib/pages/settings_page.dart index 66b527532..78ec7e44f 100644 --- a/kitchenowl/lib/pages/settings_page.dart +++ b/kitchenowl/lib/pages/settings_page.dart @@ -20,11 +20,13 @@ import 'package:kitchenowl/pages/household_update_page.dart'; import 'package:kitchenowl/pages/reports_list_page.dart'; import 'package:kitchenowl/pages/settings_server_user_page.dart'; import 'package:kitchenowl/services/api/api_service.dart'; +import 'package:kitchenowl/services/notification_service.dart'; import 'package:kitchenowl/styles/colors.dart'; import 'package:kitchenowl/widgets/settings/color_button.dart'; import 'package:kitchenowl/widgets/sliver_expansion_tile.dart'; import 'package:kitchenowl/widgets/user_list_tile.dart'; import 'package:sliver_tools/sliver_tools.dart'; +import 'package:unifiedpush_ui/unifiedpush_ui.dart'; class SettingsPage extends StatefulWidget { final Household? household; @@ -381,6 +383,30 @@ class _SettingsPageState extends State { ), ), ), + if (!kIsWeb && Platform.isAndroid || Platform.isLinux) + ListTile( + title: Text( + AppLocalizations.of(context)!.server, + ), + leading: const Icon(Icons.notifications_rounded), + onTap: () async { + state.notificationDistributor != null + ? await NotificationService.getInstance() + .unregister() + : await UnifiedPushUi( + context, + [NotificationService.instanceName], + NotificationService.getInstance(), + ).registerAppWithDialog(); + BlocProvider.of(context) + .refreshNotificationDistributor(); + }, + trailing: Padding( + padding: const EdgeInsets.only(right: 8), + child: Text(state.notificationDistributor ?? + AppLocalizations.of(context)!.none), + ), + ), ], ), ), diff --git a/kitchenowl/lib/services/notification_service.dart b/kitchenowl/lib/services/notification_service.dart new file mode 100644 index 000000000..6dc0413a3 --- /dev/null +++ b/kitchenowl/lib/services/notification_service.dart @@ -0,0 +1,169 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:unifiedpush/unifiedpush.dart'; +import 'package:unifiedpush_ui/unifiedpush_ui.dart'; + +class NotificationService extends UnifiedPushFunctions { + static const String instanceName = "org.tombursch.kitchenowl"; + + static NotificationService? _instance; + + String? endpoint; + + NotificationService.internal(); + + static NotificationService getInstance() { + _instance ??= NotificationService.internal(); + + return _instance!; + } + + Future initialize() async { + debugPrint("Initializing UnifiedPush"); + UnifiedPush.initialize( + onNewEndpoint: onNewEndpoint, + onRegistrationFailed: onRegistrationFailed, + onUnregistered: onUnregistered, + onMessage: onNotification, + linuxDBusName: instanceName, + ).then((registered) { + if (registered) { + UnifiedPush.register( + instance: instanceName, + ); + } + }); + + try { + if (!kIsWeb && Platform.isAndroid) { + final AndroidFlutterLocalNotificationsPlugin? androidImplementation = + NotificationService.getInstance() + .flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>(); + + androidImplementation?.requestNotificationsPermission(); + } + } on Exception catch (_) { + debugPrint("Exception while granting permissions"); + } + UnifiedPush.tryUseCurrentOrDefaultDistributor().then((success) { + if (success) { + UnifiedPush.register( + instance: instanceName, + ); + } + }); + } + + Future unregister() => UnifiedPush.unregister(instanceName); + + @override + Future getDistributor() => UnifiedPush.getDistributor(); + + @override + Future> getDistributors() => UnifiedPush.getDistributors(); + + @override + Future registerApp(String instance) => + UnifiedPush.registerApp(instance); + + @override + Future saveDistributor(String distributor) => + UnifiedPush.saveDistributor(distributor); + + void onNewEndpoint(PushEndpoint endpoint, String instance) { + this.endpoint = endpoint.url; + debugPrint( + "New endpoint: https://unifiedpush.org/test_wp.html#endpoint=${endpoint.url}&p256dh=${endpoint.pubKeySet?.pubKey}&auth=${endpoint.pubKeySet?.auth}"); + } + + void onRegistrationFailed(FailedReason reason, String instance) { + onUnregistered(instance); + } + + void onUnregistered(String instance) { + endpoint = null; + debugPrint("unregistered ${instance}"); + } + + final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + + static bool _notificationInitialized = false; + + Map decodeMessageContentsUri(String message) { + List uri = Uri.decodeComponent(message).split("&"); + Map decoded = {}; + for (var i in uri) { + try { + decoded[i.split("=")[0]] = i.split("=")[1]; + } on Exception { + debugPrint("Couldn't decode $i"); + } + } + return decoded; + } + + Future onNotification( + PushMessage message, + String instance, + ) async { + debugPrint("instance $instance"); + debugPrint("onNotification"); + var payload = utf8.decode(message.content); + + String title = 'KitchenOwl'; // Default title + String body = 'Could not get the content'; // Default body + + try { + // Try to decode title and message (JSON) + Map decodedMessage = decodeMessageContentsUri(payload); + title = decodedMessage['title'] ?? title; + body = decodedMessage['message'] ?? body; + } catch (e) { + // If decoding fails, use plain payload as body + body = payload.isNotEmpty ? payload : 'Empty message'; + } + + debugPrint(title); + if (!_notificationInitialized) _initNotifications(); + + var androidPlatformChannelSpecifics = const AndroidNotificationDetails( + 'KitchenOwl', + 'KitchenOwl', + playSound: false, + ); + var platformChannelSpecifics = NotificationDetails( + android: androidPlatformChannelSpecifics, + ); + await flutterLocalNotificationsPlugin.show( + DateTime.now().microsecondsSinceEpoch % 100000000, + title, + body, + platformChannelSpecifics, + ); + return true; + } + + void _initNotifications() async { + WidgetsFlutterBinding.ensureInitialized(); + + const AndroidInitializationSettings initializationSettingsAndroid = + AndroidInitializationSettings('notification_icon'); + const LinuxInitializationSettings initializationSettingsLinux = + LinuxInitializationSettings(defaultActionName: 'open'); + const InitializationSettings initializationSettings = + InitializationSettings( + android: initializationSettingsAndroid, + linux: initializationSettingsLinux, + ); + _notificationInitialized = await flutterLocalNotificationsPlugin + .initialize(initializationSettings) ?? + false; + } +} diff --git a/kitchenowl/pubspec.lock b/kitchenowl/pubspec.lock index 5a841a9d2..dab08fec2 100755 --- a/kitchenowl/pubspec.lock +++ b/kitchenowl/pubspec.lock @@ -371,6 +371,38 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: edae0c34573233ab03f5ba1f07465e55c384743893042cb19e010b4ee8541c12 + url: "https://pub.dev" + source: hosted + version: "19.3.0" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5 + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe" + url: "https://pub.dev" + source: hosted + version: "9.1.0" + flutter_local_notifications_windows: + dependency: transitive + description: + name: flutter_local_notifications_windows + sha256: f8fc0652a601f83419d623c85723a3e82ad81f92b33eaa9bcc21ea1b94773e6e + url: "https://pub.dev" + source: hosted + version: "1.0.0" flutter_localizations: dependency: "direct main" description: flutter @@ -1185,6 +1217,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + unifiedpush: + dependency: "direct main" + description: + name: unifiedpush + sha256: "1418375efb580af9640de4eaf4209cb6481f9a48792648ced3051f30e67d9568" + url: "https://pub.dev" + source: hosted + version: "6.0.2" + unifiedpush_android: + dependency: transitive + description: + name: unifiedpush_android + sha256: "2f25db8eb2fc3183bf2e43db89fff20b2587adc1c361e1d1e06b223a0d45b50a" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + unifiedpush_platform_interface: + dependency: transitive + description: + name: unifiedpush_platform_interface + sha256: bb49d2748211520e35e0374ab816faa8a2c635267e71909d334ad868d532eba5 + url: "https://pub.dev" + source: hosted + version: "3.0.1" + unifiedpush_ui: + dependency: "direct main" + description: + name: unifiedpush_ui + sha256: cf86f0214f37debd41f25c0425c8489df85e27f9f8784fed571eb7a86d39ba11 + url: "https://pub.dev" + source: hosted + version: "0.1.0" universal_html: dependency: "direct main" description: diff --git a/kitchenowl/pubspec.yaml b/kitchenowl/pubspec.yaml index 26ace79a5..a5bf1192c 100644 --- a/kitchenowl/pubspec.yaml +++ b/kitchenowl/pubspec.yaml @@ -78,6 +78,11 @@ dependencies: wakelock_plus: ^1.2.10 fuzzywuzzy: ^1.2.0 + # Notifications + unifiedpush: ^6.0.2 + unifiedpush_ui: ^0.1.0 + flutter_local_notifications: ^19.3.0 + dev_dependencies: flutter_test: sdk: flutter diff --git a/kitchenowl/windows/flutter/generated_plugins.cmake b/kitchenowl/windows/flutter/generated_plugins.cmake index a814e1474..85a914334 100644 --- a/kitchenowl/windows/flutter/generated_plugins.cmake +++ b/kitchenowl/windows/flutter/generated_plugins.cmake @@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + flutter_local_notifications_windows ) set(PLUGIN_BUNDLED_LIBRARIES) From 9d918140dece88baf3319446efffa893ca63fd0b Mon Sep 17 00:00:00 2001 From: Tom Bursch Date: Sun, 6 Jul 2025 16:00:31 +0200 Subject: [PATCH 2/2] Update --- backend/app/config.py | 2 + backend/app/models/__init__.py | 1 + .../models/push_notification_subscription.py | 41 ++++++ backend/app/models/token.py | 11 ++ backend/pyproject.toml | 1 + backend/uv.lock | 53 ++++++++ kitchenowl/android/app/build.gradle | 1 + kitchenowl/lib/app.dart | 3 +- kitchenowl/lib/cubits/settings_cubit.dart | 4 +- kitchenowl/lib/pages/settings_page.dart | 11 +- .../lib/services/notification_service.dart | 27 ++-- .../flutter/generated_plugin_registrant.cc | 12 ++ .../linux/flutter/generated_plugins.cmake | 3 + kitchenowl/pubspec.lock | 128 ++++++++++++++++-- kitchenowl/pubspec.yaml | 5 +- .../flutter/generated_plugin_registrant.cc | 9 ++ .../windows/flutter/generated_plugins.cmake | 3 + 17 files changed, 278 insertions(+), 37 deletions(-) create mode 100644 backend/app/models/push_notification_subscription.py mode change 100755 => 100644 kitchenowl/pubspec.lock diff --git a/backend/app/config.py b/backend/app/config.py index 612129925..73dc3a159 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -29,6 +29,7 @@ from flask_bcrypt import Bcrypt from flask_jwt_extended import JWTManager from flask_apscheduler import APScheduler +from flask_pywebpush import WebPush import sqlite_icu import os @@ -170,6 +171,7 @@ def get_secret(env_var: str, default: str = None) -> str | None: migrate = Migrate(app, db, render_as_batch=True) bcrypt = Bcrypt(app) jwt = JWTManager(app) +push = WebPush(app) socketio = SocketIO( app, json=app.json, diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 9af3ad90f..662702b3d 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -18,3 +18,4 @@ from .challenge_password_reset import ChallengePasswordReset from .oidc import OIDCLink, OIDCRequest from .report import Report +from .push_notification_subscription import PushNotificationSubscription diff --git a/backend/app/models/push_notification_subscription.py b/backend/app/models/push_notification_subscription.py new file mode 100644 index 000000000..7d57f46a2 --- /dev/null +++ b/backend/app/models/push_notification_subscription.py @@ -0,0 +1,41 @@ +from typing import Any, Self, TYPE_CHECKING, cast + +from app import db +from sqlalchemy.orm import Mapped + +Model = db.Model +if TYPE_CHECKING: + from app.models import Token + from app.helpers.db_model_base import DbModelBase + + Model = DbModelBase + + +class PushNotificationSubscription(Model): + __tablename__ = "push_notification_subscription" + + endpoint: Mapped[str] = db.Column(db.String(), primary_key=True) + pubkey: Mapped[str] = db.Column(db.String()) + auth: Mapped[str] = db.Column(db.String()) + + # Should never be an access token + created_by_token_id: Mapped[int] = db.Column( + db.Integer, db.ForeignKey("token.jti"), nullable=False + ) + created_by_token: Mapped["Token"] = cast( + Mapped["Token"], + db.relationship("Token"), + ) + + def toSubsciptionInfo(self) -> dict[str, Any]: + return { + "endpoint": self.endpoint, + "keys": { + "p256dh": self.pubkey, + "auth": self.auth, + }, + } + + @classmethod + def find_by_user(cls, user_id: int) -> list[Self] | None: + return cls.query.filter(cls.created_by_token.user_id == user_id).all() diff --git a/backend/app/models/token.py b/backend/app/models/token.py index 1d8c5eed2..8ec1b1639 100644 --- a/backend/app/models/token.py +++ b/backend/app/models/token.py @@ -11,6 +11,7 @@ Model = db.Model if TYPE_CHECKING: + from app.models import PushNotificationSubscription from app.helpers.db_model_base import DbModelBase Model = DbModelBase @@ -53,6 +54,16 @@ class Token(Model): lazy="selectin", ), ) + created_push_notification_subscriptions: Mapped[ + List["PushNotificationSubscription"] + ] = cast( + Mapped[List["PushNotificationSubscription"]], + db.relationship( + "PushNotificationSubscription", + back_populates="created_by_token", + cascade="all, delete-orphan", + ), + ) def obj_to_dict( self, diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 5463c611c..fc0e4da3f 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -42,6 +42,7 @@ dependencies = [ "psycopg2-binary>=2.9.10", "uWSGI>=2.0.28", "uwsgi-tools>=1.1.1", + "flask-pywebpush>=1.1", ] [dependency-groups] diff --git a/backend/uv.lock b/backend/uv.lock index 049f3bf5e..d73596f62 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -737,6 +737,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/c4/3f329b23d769fe7628a5fc57ad36956f1fb7132cf8837be6da762b197327/Flask_Migrate-4.1.0-py3-none-any.whl", hash = "sha256:24d8051af161782e0743af1b04a152d007bad9772b2bca67b7ec1e8ceeb3910d", size = 21237, upload-time = "2025-01-10T18:51:09.527Z" }, ] +[[package]] +name = "flask-pywebpush" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "pywebpush" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/b3/65eefcdfbb2381cb1d58a9973bfdfb113178c6a9633eb384e0d0a39cd36b/Flask-pyWebPush-1.1.tar.gz", hash = "sha256:aa6b6391f35b77727948fd416a5bb4051fac916d53c5dccb8af35ff768741a96", size = 3487, upload-time = "2020-03-30T22:39:00.2Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/df/74ca48a77785d5c5d02eb80202d59ac66ef25c880f585559968f71227df8/Flask_pyWebPush-1.1-py3-none-any.whl", hash = "sha256:018187d9deac755ebae9fd774801704b08540c50dd0856a1e8599fcbc9e57239", size = 4291, upload-time = "2020-03-30T22:38:58.626Z" }, +] + [[package]] name = "flask-socketio" version = "5.5.1" @@ -979,6 +992,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173, upload-time = "2020-06-22T23:32:36.781Z" }, ] +[[package]] +name = "http-ece" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/af/249d1576653b69c20b9ac30e284b63bd94af6a175d72d87813235caf2482/http_ece-1.2.1.tar.gz", hash = "sha256:8c6ab23116bbf6affda894acfd5f2ca0fb8facbcbb72121c11c75c33e7ce8cff", size = 8830, upload-time = "2024-08-08T00:10:47.301Z" } + [[package]] name = "httpcore" version = "1.0.9" @@ -1204,6 +1226,7 @@ dependencies = [ { name = "flask-bcrypt" }, { name = "flask-jwt-extended" }, { name = "flask-migrate" }, + { name = "flask-pywebpush" }, { name = "flask-socketio" }, { name = "flask-sqlalchemy" }, { name = "gevent" }, @@ -1254,6 +1277,7 @@ requires-dist = [ { name = "flask-bcrypt", specifier = ">=1.0.1" }, { name = "flask-jwt-extended", specifier = ">=4.7.1" }, { name = "flask-migrate", specifier = ">=4.1.0" }, + { name = "flask-pywebpush", specifier = ">=1.1" }, { name = "flask-socketio", specifier = ">=5.5.1" }, { name = "flask-sqlalchemy", specifier = ">=3.1.1" }, { name = "gevent", specifier = ">=24.11.1" }, @@ -1889,6 +1913,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913, upload-time = "2025-10-10T11:13:57.058Z" }, ] +[[package]] +name = "py-vapid" +version = "1.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/57/5c1c61f27ce01f939443cf3f6c279a295f7ec0327b18a1cbbcfefe0b5456/py_vapid-1.9.2.tar.gz", hash = "sha256:3c8973b6cf8384ad0c9ae64d6270ccc480e0b92c702d8f5ea2cc03e6b51247f9", size = 20300, upload-time = "2024-11-19T21:55:41.859Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/fb/b877a221b09dabcebeb073d5e7f19244f3fa1d5aec87092c359a6049a006/py_vapid-1.9.2-py3-none-any.whl", hash = "sha256:4ccf8a00fc54f1f99f66fb543c96f2c82622508ad814b6e9225f2c26948934d7", size = 21492, upload-time = "2024-11-19T21:55:40.832Z" }, +] + [[package]] name = "pycparser" version = "2.23" @@ -2145,6 +2181,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] +[[package]] +name = "pywebpush" +version = "2.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "cryptography" }, + { name = "http-ece" }, + { name = "py-vapid" }, + { name = "requests" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/ca/6e669bf676916d66c8c7adedc291e9a9758650f9d85ec040fda13e3c82f4/pywebpush-2.0.3.tar.gz", hash = "sha256:584878e3c243e873a22db8895505d95715bc796ef74cc1b8fe99f596174161e3", size = 25874, upload-time = "2024-11-19T21:30:20.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/8a/ecaa2a589338a038b89148b01a5db2dac53e45918342da69baca1fb058fc/pywebpush-2.0.3-py3-none-any.whl", hash = "sha256:04666441715bc547918d7668b2ac7ad5c4b5de7d0a6cf528daf61e0c4bc5431c", size = 21364, upload-time = "2024-11-19T21:30:19.312Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" diff --git a/kitchenowl/android/app/build.gradle b/kitchenowl/android/app/build.gradle index bff8fa34e..e13c39ab1 100644 --- a/kitchenowl/android/app/build.gradle +++ b/kitchenowl/android/app/build.gradle @@ -34,6 +34,7 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' // required by local notifications implementation 'androidx.window:window:1.0.0' // required by local notifications implementation 'androidx.window:window-java:1.0.0' // required by local notifications + implementation 'org.unifiedpush.android:embedded-fcm-distributor:3.0.0' // required by unified push } configurations.configureEach { diff --git a/kitchenowl/lib/app.dart b/kitchenowl/lib/app.dart index 145d0d0cf..1184f01ae 100644 --- a/kitchenowl/lib/app.dart +++ b/kitchenowl/lib/app.dart @@ -111,7 +111,8 @@ class _AppState extends State { ); } - NotificationService.getInstance().initialize(); + NotificationService.getInstance().initialize().whenComplete( + () => widget._settingsCubit.refreshNotificationDistributor()); } @override diff --git a/kitchenowl/lib/cubits/settings_cubit.dart b/kitchenowl/lib/cubits/settings_cubit.dart index d58f39639..2e75cfee2 100644 --- a/kitchenowl/lib/cubits/settings_cubit.dart +++ b/kitchenowl/lib/cubits/settings_cubit.dart @@ -53,8 +53,6 @@ class SettingsCubit extends Cubit { shoppingListTapToRemove: await shoppingListTapToRemove ?? true, recentItemsCategorize: await recentItemsCategorize ?? false, restoreLastShoppingList: await restoreLastShoppingList ?? false, - notificationDistributor: - await NotificationService.getInstance().getDistributor(), )); } @@ -62,7 +60,7 @@ class SettingsCubit extends Cubit { await Future.delayed(const Duration(seconds: 1)); String? distributor = await NotificationService.getInstance().getDistributor(); - print("Refresh: ${distributor}"); + print("Refresh distributor: ${distributor}"); emit(state.copyWith(notificationDistributor: Nullable(distributor))); } diff --git a/kitchenowl/lib/pages/settings_page.dart b/kitchenowl/lib/pages/settings_page.dart index 78ec7e44f..5d164586e 100644 --- a/kitchenowl/lib/pages/settings_page.dart +++ b/kitchenowl/lib/pages/settings_page.dart @@ -383,7 +383,7 @@ class _SettingsPageState extends State { ), ), ), - if (!kIsWeb && Platform.isAndroid || Platform.isLinux) + if (!kIsWeb && (Platform.isAndroid || Platform.isLinux)) ListTile( title: Text( AppLocalizations.of(context)!.server, @@ -394,9 +394,12 @@ class _SettingsPageState extends State { ? await NotificationService.getInstance() .unregister() : await UnifiedPushUi( - context, - [NotificationService.instanceName], - NotificationService.getInstance(), + context: context, + instances: [NotificationService.instanceName], + unifiedPushFunctions: + NotificationService.getInstance(), + showNoDistribDialog: true, + onNoDistribDialogDismissed: () {}, ).registerAppWithDialog(); BlocProvider.of(context) .refreshNotificationDistributor(); diff --git a/kitchenowl/lib/services/notification_service.dart b/kitchenowl/lib/services/notification_service.dart index 6dc0413a3..21f4f2ecc 100644 --- a/kitchenowl/lib/services/notification_service.dart +++ b/kitchenowl/lib/services/notification_service.dart @@ -5,6 +5,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:unifiedpush/unifiedpush.dart'; +import 'package:unifiedpush_platform_interface/unifiedpush_platform_interface.dart'; +import 'package:unifiedpush_storage_shared_preferences/storage.dart'; import 'package:unifiedpush_ui/unifiedpush_ui.dart'; class NotificationService extends UnifiedPushFunctions { @@ -29,14 +31,12 @@ class NotificationService extends UnifiedPushFunctions { onRegistrationFailed: onRegistrationFailed, onUnregistered: onUnregistered, onMessage: onNotification, - linuxDBusName: instanceName, - ).then((registered) { - if (registered) { - UnifiedPush.register( - instance: instanceName, - ); - } - }); + linuxOptions: LinuxOptions( + dbusName: instanceName, + storage: UnifiedPushStorageSharedPreferences(), + background: false, + ), + ); try { if (!kIsWeb && Platform.isAndroid) { @@ -51,11 +51,11 @@ class NotificationService extends UnifiedPushFunctions { } on Exception catch (_) { debugPrint("Exception while granting permissions"); } - UnifiedPush.tryUseCurrentOrDefaultDistributor().then((success) { + + UnifiedPush.tryUseCurrentOrDefaultDistributor().then((success) async { if (success) { - UnifiedPush.register( - instance: instanceName, - ); + String? distributor = await getDistributor(); + if (distributor != null) registerApp(distributor); } }); } @@ -113,8 +113,6 @@ class NotificationService extends UnifiedPushFunctions { PushMessage message, String instance, ) async { - debugPrint("instance $instance"); - debugPrint("onNotification"); var payload = utf8.decode(message.content); String title = 'KitchenOwl'; // Default title @@ -130,7 +128,6 @@ class NotificationService extends UnifiedPushFunctions { body = payload.isNotEmpty ? payload : 'Empty message'; } - debugPrint(title); if (!_notificationInitialized) _initNotifications(); var androidPlatformChannelSpecifics = const AndroidNotificationDetails( diff --git a/kitchenowl/linux/flutter/generated_plugin_registrant.cc b/kitchenowl/linux/flutter/generated_plugin_registrant.cc index 80530edab..7d55788c5 100644 --- a/kitchenowl/linux/flutter/generated_plugin_registrant.cc +++ b/kitchenowl/linux/flutter/generated_plugin_registrant.cc @@ -9,7 +9,10 @@ #include #include #include +#include #include +#include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) dynamic_system_colors_registrar = @@ -21,7 +24,16 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); + g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); + screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); + g_autoptr(FlPluginRegistrar) webcrypto_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WebcryptoPlugin"); + webcrypto_plugin_register_with_registrar(webcrypto_registrar); + g_autoptr(FlPluginRegistrar) window_manager_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); + window_manager_plugin_register_with_registrar(window_manager_registrar); } diff --git a/kitchenowl/linux/flutter/generated_plugins.cmake b/kitchenowl/linux/flutter/generated_plugins.cmake index e5db312d1..7c386f700 100644 --- a/kitchenowl/linux/flutter/generated_plugins.cmake +++ b/kitchenowl/linux/flutter/generated_plugins.cmake @@ -6,7 +6,10 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_system_colors file_selector_linux flutter_secure_storage_linux + screen_retriever_linux url_launcher_linux + webcrypto + window_manager ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/kitchenowl/pubspec.lock b/kitchenowl/pubspec.lock old mode 100755 new mode 100644 index dab08fec2..580956633 --- a/kitchenowl/pubspec.lock +++ b/kitchenowl/pubspec.lock @@ -375,10 +375,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: edae0c34573233ab03f5ba1f07465e55c384743893042cb19e010b4ee8541c12 + sha256: "19ffb0a8bb7407875555e5e98d7343a633bb73707bae6c6a5f37c90014077875" url: "https://pub.dev" source: hosted - version: "19.3.0" + version: "19.5.0" flutter_local_notifications_linux: dependency: transitive description: @@ -399,10 +399,10 @@ packages: dependency: transitive description: name: flutter_local_notifications_windows - sha256: f8fc0652a601f83419d623c85723a3e82ad81f92b33eaa9bcc21ea1b94773e6e + sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.3" flutter_localizations: dependency: "direct main" description: flutter @@ -660,6 +660,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -924,6 +932,46 @@ packages: url: "https://pub.dev" source: hosted version: "0.28.0" + screen_retriever: + dependency: transitive + description: + name: screen_retriever + sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_linux: + dependency: transitive + description: + name: screen_retriever_linux + sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_macos: + dependency: transitive + description: + name: screen_retriever_macos + sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_platform_interface: + dependency: transitive + description: + name: screen_retriever_platform_interface + sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_windows: + dependency: transitive + description: + name: screen_retriever_windows + sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13" + url: "https://pub.dev" + source: hosted + version: "0.2.0" share_handler: dependency: "direct main" description: @@ -1193,6 +1241,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.7" + timezone: + dependency: transitive + description: + name: timezone + sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 + url: "https://pub.dev" + source: hosted + version: "0.10.1" transparent_image: dependency: "direct main" description: @@ -1221,34 +1277,58 @@ packages: dependency: "direct main" description: name: unifiedpush - sha256: "1418375efb580af9640de4eaf4209cb6481f9a48792648ced3051f30e67d9568" + sha256: "8ed9767f750a1dc6159a77e2171641d0cb825dc87682d1ce1b8618689b79f58e" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.2.0" unifiedpush_android: dependency: transitive description: name: unifiedpush_android - sha256: "2f25db8eb2fc3183bf2e43db89fff20b2587adc1c361e1d1e06b223a0d45b50a" + sha256: "2e6684e0a1a39ad8d6c7bc0d197954d50686acb1b1a7614b18ab0e0126f5492a" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.4.1" + unifiedpush_linux: + dependency: transitive + description: + name: unifiedpush_linux + sha256: c062d5eedd1cec70bcd33270cc4e01ae0ff6501f33d471167c06b34a968adfeb + url: "https://pub.dev" + source: hosted + version: "1.0.0" unifiedpush_platform_interface: dependency: transitive description: name: unifiedpush_platform_interface - sha256: bb49d2748211520e35e0374ab816faa8a2c635267e71909d334ad868d532eba5 + sha256: "83372bc8d794b8b12ef6993b518d7be907dcfc2191bdf6de0ece5c4445d89880" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + unifiedpush_storage_interface: + dependency: transitive + description: + name: unifiedpush_storage_interface + sha256: b8d423a4695efc616aa21d8ab48fb5ef99d6288c68b56282b8faac1579ceabd9 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + unifiedpush_storage_shared_preferences: + dependency: "direct main" + description: + name: unifiedpush_storage_shared_preferences + sha256: eda9c52bac0058f81e0d9c65e33eedc12c5901d2e68ad47fdf68175ddf00a3b4 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "1.0.0" unifiedpush_ui: dependency: "direct main" description: name: unifiedpush_ui - sha256: cf86f0214f37debd41f25c0425c8489df85e27f9f8784fed571eb7a86d39ba11 + sha256: "1b36b2aa0bc6b61577e2661c1183bd3442969ecf77b4c78174796d324f66dd1d" url: "https://pub.dev" source: hosted - version: "0.1.0" + version: "0.2.0" universal_html: dependency: "direct main" description: @@ -1401,6 +1481,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + webcrypto: + dependency: transitive + description: + name: webcrypto + sha256: e393b3d0b01694a8f81efecf278ed7392877130e6e7b29f578863e4f2d0b2ebd + url: "https://pub.dev" + source: hosted + version: "0.5.8" webdriver: dependency: transitive description: @@ -1409,6 +1497,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + webpush_encryption: + dependency: transitive + description: + name: webpush_encryption + sha256: "63046b7d6909f4a72ce3c153fa574726e257aaf21b1995ba063dc241a1b1520b" + url: "https://pub.dev" + source: hosted + version: "1.0.0" win32: dependency: transitive description: @@ -1425,6 +1521,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + window_manager: + dependency: transitive + description: + name: window_manager + sha256: "7eb6d6c4164ec08e1bf978d6e733f3cebe792e2a23fb07cbca25c2872bfdbdcd" + url: "https://pub.dev" + source: hosted + version: "0.5.1" xdg_directories: dependency: transitive description: diff --git a/kitchenowl/pubspec.yaml b/kitchenowl/pubspec.yaml index a5bf1192c..208b37b59 100644 --- a/kitchenowl/pubspec.yaml +++ b/kitchenowl/pubspec.yaml @@ -79,8 +79,9 @@ dependencies: fuzzywuzzy: ^1.2.0 # Notifications - unifiedpush: ^6.0.2 - unifiedpush_ui: ^0.1.0 + unifiedpush: ^6.2.0 + unifiedpush_ui: ^0.2.0 + unifiedpush_storage_shared_preferences: ^1.0.0 flutter_local_notifications: ^19.3.0 dev_dependencies: diff --git a/kitchenowl/windows/flutter/generated_plugin_registrant.cc b/kitchenowl/windows/flutter/generated_plugin_registrant.cc index 6783f76c3..78824a9cd 100644 --- a/kitchenowl/windows/flutter/generated_plugin_registrant.cc +++ b/kitchenowl/windows/flutter/generated_plugin_registrant.cc @@ -9,8 +9,11 @@ #include #include #include +#include #include #include +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { DynamicColorPluginCApiRegisterWithRegistrar( @@ -19,8 +22,14 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FileSelectorWindows")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); + WebcryptoPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WebcryptoPlugin")); + WindowManagerPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WindowManagerPlugin")); } diff --git a/kitchenowl/windows/flutter/generated_plugins.cmake b/kitchenowl/windows/flutter/generated_plugins.cmake index 85a914334..0f0fd18f4 100644 --- a/kitchenowl/windows/flutter/generated_plugins.cmake +++ b/kitchenowl/windows/flutter/generated_plugins.cmake @@ -6,8 +6,11 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_system_colors file_selector_windows flutter_secure_storage_windows + screen_retriever_windows share_plus url_launcher_windows + webcrypto + window_manager ) list(APPEND FLUTTER_FFI_PLUGIN_LIST