Watch
1
0
Fork
You've already forked nexus
0
forked from Nexus/nexus

Compare commits

..
Author SHA1 Message Date
129f0794f2
add wip deregistering 2026-08-26 16:27:43 -04:00
0122feae1d
smarter notif logic 2026-08-26 11:55:20 -04:00
74207e405b
include avatars on notifs 2026-08-26 00:46:57 -04:00
2beb6dcb9b
send notifications natively 2026-08-26 00:25:47 -04:00
11d83ee067
override webcrypto deps 2026-08-25 20:18:47 -04:00
02704e97ef
pass pushes to gomuks 2026-08-25 19:51:43 -04:00
adf3da8d77
working registration 2026-08-25 17:30:55 -04:00
5ed6e5e083
slight cleanup
`onNewEndpoint` is still not getting called for some reason (help appreciated)
2026-08-23 22:13:38 -04:00
7218eb5195
add UnifiedPushAllowedController 2026-08-23 14:26:51 -04:00
fe871217b2
support for registering pushers 2026-08-23 14:09:53 -04:00
33 changed files with 765 additions and 84 deletions

View file

@ -11,6 +11,8 @@
"muks", "muks",
"prefs", "prefs",
"unban", "unban",
"unredact" "unifiedpush",
"unredact",
"webpush"
] ]
} }

View file

@ -190,6 +190,12 @@ dart scripts/generate.dart
> export CPATH="$(clang -v 2>&1 | grep "Selected GCC installation" | rev | cut -d' ' -f1 | rev)/include" > export CPATH="$(clang -v 2>&1 | grep "Selected GCC installation" | rev | cut -d' ' -f1 | rev)/include"
> ``` > ```
Build webcrypto:
```sh
flutter pub run webcrypto:setup
```
Build generated files, and watch for new changes: Build generated files, and watch for new changes:
```sh ```sh

View file

@ -20,16 +20,21 @@ android {
ndkVersion = flutter.ndkVersion ndkVersion = flutter.ndkVersion
compileOptions { compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_21 sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21
} }
defaultConfig { defaultConfig {
applicationId = "nexus.federated.nexus" applicationId = "nexus.federated.nexus"
minSdk = 29 minSdk = 29
targetSdk = flutter.targetSdkVersion targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode versionCode = flutter.versionCode
versionName = flutter.versionName versionName = flutter.versionName
multiDexEnabled = true
} }
signingConfigs { signingConfigs {
@ -63,6 +68,11 @@ android {
} }
} }
dependencies {
implementation("org.unifiedpush.android:embedded-fcm-distributor:3.1.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
}
kotlin { kotlin {
compilerOptions { compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21 jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21

2
gomuks

@ -1 +1 @@
Subproject commit 0e06c7b74f96d3e29a8101893ca1921d13a90573 Subproject commit 6c8b2781e02f27b64f37735cba6634784f117b88

View file

@ -7,6 +7,7 @@ import UIKit
_ application: UIApplication, _ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }

View file

@ -1,3 +1,4 @@
import "dart:async";
import "dart:ffi"; import "dart:ffi";
import "dart:io"; import "dart:io";
import "dart:isolate"; import "dart:isolate";
@ -15,11 +16,13 @@ import "package:nexus/controllers/sync_status.dart";
import "package:nexus/controllers/top_level_spaces.dart"; import "package:nexus/controllers/top_level_spaces.dart";
import "package:nexus/helpers/extensions/gomuks_buffer.dart"; import "package:nexus/helpers/extensions/gomuks_buffer.dart";
import "package:nexus/main.dart"; import "package:nexus/main.dart";
import "package:nexus/models/capabilities.dart";
import "package:nexus/models/content/message.dart"; import "package:nexus/models/content/message.dart";
import "package:nexus/models/event.dart"; import "package:nexus/models/event.dart";
import "package:nexus/models/oauth_auth_code_response.dart"; import "package:nexus/models/oauth_auth_code_response.dart";
import "package:nexus/models/open_graph_data.dart"; import "package:nexus/models/open_graph_data.dart";
import "package:nexus/models/paginate.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/download_media.dart";
import "package:nexus/models/requests/get_event.dart"; import "package:nexus/models/requests/get_event.dart";
import "package:nexus/models/requests/get_related_events.dart"; import "package:nexus/models/requests/get_related_events.dart";
@ -31,6 +34,7 @@ import "package:nexus/models/requests/oauth/get_auth_url.dart";
import "package:nexus/models/requests/oauth/register_client.dart"; import "package:nexus/models/requests/oauth/register_client.dart";
import "package:nexus/models/requests/paginate.dart"; import "package:nexus/models/requests/paginate.dart";
import "package:nexus/models/requests/redact_event.dart"; import "package:nexus/models/requests/redact_event.dart";
import "package:nexus/models/requests/register_pusher.dart";
import "package:nexus/models/requests/report.dart"; import "package:nexus/models/requests/report.dart";
import "package:nexus/models/requests/send_event.dart"; import "package:nexus/models/requests/send_event.dart";
import "package:nexus/models/requests/send_message.dart"; import "package:nexus/models/requests/send_message.dart";
@ -154,18 +158,15 @@ class ClientController extends AsyncNotifier<int> {
throw Exception("GomuksStart returned error code $errorCode"); throw Exception("GomuksStart returned error code $errorCode");
} }
Future<dynamic> _sendCommand( Future<dynamic> callGomuksMethod(
String command, [ Map<String, dynamic> data,
Map<String, dynamic> data = const {}, FutureOr<GomuksResponse> Function(int handle, GomuksBorrowedBuffer data)
]) async { callback,
) async {
final bufferPointer = data.toGomuksBufferPtr(); final bufferPointer = data.toGomuksBufferPtr();
final handle = await future; final handle = await future;
final response = await Isolate.run( final response = await Isolate.run(
() => GomuksSubmitCommand( () => callback(handle, bufferPointer.ref),
handle,
command.toNativeUtf8().cast<Char>(),
bufferPointer.ref,
),
); );
calloc.free(bufferPointer); calloc.free(bufferPointer);
@ -178,6 +179,24 @@ class ClientController extends AsyncNotifier<int> {
return json; return json;
} }
Future<Event?> handlePush(Map<String, dynamic> data) async {
final response = await callGomuksMethod(
data,
(handle, data) async => GomuksHandlePush(handle, data),
);
return response == null ? null : .fromJson(response);
}
Future<dynamic> _sendCommand(
String command, [
Map<String, dynamic> data = const {},
]) => callGomuksMethod(
data,
(handle, data) async =>
GomuksSubmitCommand(handle, command.toNativeUtf8().cast<Char>(), data),
);
Future<void> redactEvent(RedactEventRequest report) => Future<void> redactEvent(RedactEventRequest report) =>
_sendCommand("redact_event", report.toJson()); _sendCommand("redact_event", report.toJson());
@ -262,13 +281,8 @@ class ClientController extends AsyncNotifier<int> {
Future<Paginate> paginate(PaginateRequest request) async => Future<Paginate> paginate(PaginateRequest request) async =>
.fromJson(await _sendCommand("paginate", request.toJson())); .fromJson(await _sendCommand("paginate", request.toJson()));
Future<ProfileResponse> getProfile(String userId) async { Future<ProfileResponse> getProfile(String userId) async =>
try { .fromJson(await _sendCommand("get_profile", {"user_id": userId}));
return .fromJson(await _sendCommand("get_profile", {"user_id": userId}));
} catch (_) {
return ProfileResponse(profile: .new(id: userId));
}
}
Future<void> reportEvent(ReportRequest request) => Future<void> reportEvent(ReportRequest request) =>
_sendCommand("report_event", request.toJson()); _sendCommand("report_event", request.toJson());
@ -279,6 +293,15 @@ class ClientController extends AsyncNotifier<int> {
Future<void> setAccountData(SetAccountDataRequest request) => Future<void> setAccountData(SetAccountDataRequest request) =>
_sendCommand("set_account_data", request.toJson()); _sendCommand("set_account_data", request.toJson());
Future<void> registerPusher(RegisterPusherRequest request) =>
_sendCommand("register_homeserver_push", request.toJson());
Future<void> deregisterPusher(DeregisterPusherRequest request) =>
_sendCommand("register_homeserver_push", {
...request.toJson(),
"kind": null,
});
Future<MessageContent> uploadMedia(UploadMediaRequest request) async => Future<MessageContent> uploadMedia(UploadMediaRequest request) async =>
.fromJson(await _sendCommand("upload_media", request.toJson())); .fromJson(await _sendCommand("upload_media", request.toJson()));
@ -316,6 +339,10 @@ class ClientController extends AsyncNotifier<int> {
Future<SpecVersionsResponse> getSpecVersions() async => Future<SpecVersionsResponse> getSpecVersions() async =>
.fromJson(await _sendCommand("get_versions")); .fromJson(await _sendCommand("get_versions"));
Future<Capabilities> getCapabilities() async => Capabilities.fromJson(
(await _sendCommand("get_capabilities"))["capabilities"],
);
Future<Uri?> discoverHomeserver(Uri homeserver) async { Future<Uri?> discoverHomeserver(Uri homeserver) async {
try { try {
final response = await _sendCommand("discover_homeserver", { final response = await _sendCommand("discover_homeserver", {

View file

@ -0,0 +1,120 @@
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/pages/notifications.dart";
class NotificationsController
extends AsyncNotifier<FlutterLocalNotificationsPlugin> {
@override
Future<FlutterLocalNotificationsPlugin> build() async {
final notifications = FlutterLocalNotificationsPlugin();
final darwin = DarwinInitializationSettings();
await notifications.initialize(
settings: .new(
windows: .new(
appName: "Nexus",
appUserModelId: "nexus.federated.nexus",
guid: "dde78daf-130f-4e46-a80a-e31deeab45d7",
),
android: .new("ic_launcher_foreground"),
iOS: darwin,
macOS: darwin,
linux: .new(defaultActionName: "Open"),
),
onDidReceiveNotificationResponse: (details) {
if (details.payload case final eventId?) {
if (navigatorKey.currentContext case final context?) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => NotificationsPage(eventId: eventId),
),
);
}
}
},
);
return notifications;
}
Future<bool> requestPermissions() async {
final controller = await future;
if (Platform.isIOS) {
return await controller
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin
>()
?.requestPermissions(alert: true, badge: true, sound: true) ??
true;
} else if (Platform.isMacOS) {
return await controller
.resolvePlatformSpecificImplementation<
MacOSFlutterLocalNotificationsPlugin
>()
?.requestPermissions(alert: true, badge: true, sound: true) ??
true;
} else if (Platform.isAndroid) {
return await controller
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin
>()
?.requestNotificationsPermission() ??
true;
}
return true;
}
Future<void> send({
required int id,
required String title,
String? body,
File? icon,
String? payload,
}) async {
final notificationDetails = NotificationDetails(
android: .new(
"messages",
"Messages",
largeIcon: icon == null ? null : FilePathAndroidBitmap(icon.path),
),
linux: .new(
customHints: icon == null
? null
: [
.new(
name: "image-path",
value: LinuxHintStringValue(icon.path),
),
],
),
// TODO: See if icons can be added to iOS, macOS, and Windows notifications (#68)
iOS: .new(),
macOS: .new(),
windows: .new(),
);
final controller = await future;
await controller.show(
id: id,
title: title,
body: body,
notificationDetails: notificationDetails,
payload: payload,
);
}
static final provider =
AsyncNotifierProvider<
NotificationsController,
FlutterLocalNotificationsPlugin
>(NotificationsController.new);
}

View file

@ -1,15 +1,19 @@
import "dart:io"; import "dart:io";
import "package:fast_immutable_collections/fast_immutable_collections.dart"; 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:material_ui/material_ui.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:intl/intl.dart"; import "package:intl/intl.dart";
import "package:m3e_buttons/m3e_buttons.dart"; import "package:m3e_buttons/m3e_buttons.dart";
import "package:nexus/controllers/account_data.dart"; import "package:nexus/controllers/account_data.dart";
import "package:nexus/controllers/client.dart"; import "package:nexus/controllers/client.dart";
import "package:nexus/controllers/client_state.dart"; import "package:nexus/controllers/client_state.dart";
import "package:nexus/controllers/notifications.dart";
import "package:nexus/controllers/settings.dart"; import "package:nexus/controllers/settings.dart";
import "package:nexus/controllers/unified_push.dart";
import "package:nexus/controllers/spec_versions.dart"; import "package:nexus/controllers/spec_versions.dart";
import "package:nexus/controllers/unified_push_allowed.dart";
import "package:nexus/models/account_data.dart"; import "package:nexus/models/account_data.dart";
import "package:nexus/models/settings_category.dart"; import "package:nexus/models/settings_category.dart";
import "package:nexus/main.dart"; import "package:nexus/main.dart";
@ -95,6 +99,90 @@ class SettingsSectionsController
if (ref.watch(ClientStateController.provider)?.isLoggedIn == true) if (ref.watch(ClientStateController.provider)?.isLoggedIn == true)
"Account": .new([ "Account": .new([
.new(title: "Profile", icon: Icons.person, settings: .new([])), .new(title: "Profile", icon: Icons.person, settings: .new([])),
.new(
title: "Notifications",
icon: Icons.notifications,
settings: .new([
.new(
title: "Push notifications",
description: "Enable push notifications using Web Push",
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(
unifiedPushAllowed.maybeWhen(
data: (data) => data,
orElse: () => null,
) ??
description,
),
secondary: Icon(icon),
value: pusherRegistered.maybeWhen(
data: (value) => value,
orElse: () => false,
),
onChanged: loading.value
? null
: unifiedPushAllowed.maybeWhen(
data: (data) => data == null,
orElse: () => false,
)
? pusherRegistered.maybeWhen(
data: (_) => (value) async {
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,
)
.deregister();
}
} catch (error, stackTrace) {
showError(error, stackTrace);
} finally {
loading.value = false;
}
},
orElse: () => null,
)
: null,
);
},
),
icon: Icons.notification_add,
),
]),
),
.new( .new(
title: "Safety", title: "Safety",
icon: Icons.gpp_good, icon: Icons.gpp_good,
@ -166,6 +254,8 @@ class SettingsSectionsController
onPressed: () async { onPressed: () async {
Navigator.of(context) Navigator.of(context)
.popUntil((route) => route.isFirst); .popUntil((route) => route.isFirst);
Navigator.of(context)
.popUntil((route) => route.isFirst);
await WidgetsBinding.instance.endOfFrame; await WidgetsBinding.instance.endOfFrame;

View file

@ -0,0 +1,150 @@
import "dart:convert";
import "dart:io";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:intl/intl.dart";
import "package:nexus/controllers/key.dart";
import "package:nexus/controllers/notifications.dart";
import "package:nexus/controllers/rooms.dart";
import "package:nexus/main.dart";
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/register_pusher.dart";
import "package:unifiedpush/unifiedpush.dart";
import "package:unifiedpush_storage_shared_preferences/storage.dart";
import "package:window_manager/window_manager.dart";
class UnifiedPushController extends AsyncNotifier<bool> {
@override
Future<bool> build() async {
final client = ref.watch(ClientController.provider.notifier);
final registered = await UnifiedPush.initialize(
linuxOptions: .new(
dbusName: "nexus.federated.nexus.UnifiedPush",
storage: UnifiedPushStorageSharedPreferences(),
background: isInBackground,
),
onNewEndpoint: (endpoint, instance) => client.registerPusher(
.new(
appDisplayName: "Nexus",
appId: "nexus.federated.nexus",
data: PusherData.webPush(
url: Uri.parse(endpoint.url),
auth: endpoint.pubKeySet!.auth,
),
deviceDisplayName:
"Nexus on ${toBeginningOfSentenceCase(Platform.operatingSystem)}",
kind: .webPush,
lang: "en",
pushKey: endpoint.pubKeySet!.pubKey,
),
),
onMessage: (message, instance) async {
if (message.decrypted == false) {
throw Exception(
"Failed to decrypt notification. Try toggling off and on UnifiedPush in settings.",
);
}
final event = await client.handlePush(
json.decode(String.fromCharCodes(message.content)),
);
if (event == null ||
event.unreadType?.shouldNotify != true ||
(!isInBackground &&
await windowManager.isFocused().onError((_, _) => false) &&
ref.watch(KeyController.provider(KeyController.roomKey)) ==
event.roomId)) {
return;
}
final room = ref.read(
RoomsController.provider.select((rooms) => rooms[event.roomId]),
);
final avatar = room?.metadata?.avatar;
final icon = avatar == null
? null
: await ref
.read(ClientController.provider.notifier)
.downloadMedia(.new(mxc: avatar, isAvatar: true));
await ref
.read(NotificationsController.provider.notifier)
.send(
id: event.eventId.hashCode & 0x7fffffff,
title: room?.metadata?.name ?? "New Event",
icon: icon,
payload: event.eventId,
body: switch (event.content) {
MessageContent(:final body?) ||
StickerContent(:final body) => body,
_ => null,
},
);
if (isInBackground) exit(0);
},
onRegistrationFailed: (error, instance) => throw error,
onUnregistered: (instance) => ref.invalidateSelf(),
);
if (registered) {
// Needs to be registered every startup
await register(true);
}
return registered;
}
Future<void> register([bool alreadyRegistered = false]) async {
final clientState = ref.watch(ClientStateController.provider);
if (clientState?.deviceId == null) ref.invalidateSelf();
final capabilities = await ref
.watch(ClientController.provider.notifier)
.getCapabilities();
if (capabilities.webpush?.vapid == null) {
throw UnsupportedError(
"Your homeserver does not support MSC4174 (Web Push), and therefore cannot send notifications to Nexus.",
);
}
if (!alreadyRegistered &&
!await UnifiedPush.tryUseCurrentOrDefaultDistributor()) {
throw Exception("No UnifiedPush distributors found");
}
await UnifiedPush.register(
instance: clientState!.deviceId!,
vapid: capabilities.webpush?.vapid,
);
if (!alreadyRegistered) ref.invalidateSelf();
}
Future<void> 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, bool>(
UnifiedPushController.new,
);
}

View file

@ -0,0 +1,33 @@
import "dart:io";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client.dart";
import "package:unifiedpush/unifiedpush.dart";
class UnifiedPushAllowedController extends AsyncNotifier<String?> {
@override
Future<String?> build() async {
if (!await UnifiedPush.tryUseCurrentOrDefaultDistributor()) {
return "No valid distributors found. Try installing ${Platform.isLinux
? "KUnifiedPush"
: Platform.isAndroid
? "FCM or NTFY"
: "NTFY"}.";
}
final capabilities = await ref
.watch(ClientController.provider.notifier)
.getCapabilities();
if (capabilities.webpush?.vapid == null) {
return "Your homeserver does not support MSC4174 (Web Push), and therefore cannot send notifications to Nexus.";
}
return null;
}
static final provider =
AsyncNotifierProvider.autoDispose<UnifiedPushAllowedController, String?>(
UnifiedPushAllowedController.new,
);
}

View file

@ -8,8 +8,10 @@ import "package:media_kit/media_kit.dart";
import "package:nexus/controllers/client.dart"; import "package:nexus/controllers/client.dart";
import "package:nexus/controllers/client_state.dart"; import "package:nexus/controllers/client_state.dart";
import "package:nexus/controllers/multi_provider.dart"; import "package:nexus/controllers/multi_provider.dart";
import "package:nexus/controllers/notifications.dart";
import "package:nexus/controllers/settings.dart"; import "package:nexus/controllers/settings.dart";
import "package:nexus/controllers/shared_prefs.dart"; import "package:nexus/controllers/shared_prefs.dart";
import "package:nexus/controllers/unified_push.dart";
import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/scheme_to_theme.dart"; import "package:nexus/helpers/extensions/scheme_to_theme.dart";
import "package:nexus/helpers/font_licenses.dart"; import "package:nexus/helpers/font_licenses.dart";
@ -22,6 +24,7 @@ import "package:window_manager/window_manager.dart";
import "package:material_ui/material_ui.dart"; import "package:material_ui/material_ui.dart";
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
late final bool isInBackground;
final class Logger extends ProviderObserver { final class Logger extends ProviderObserver {
@override @override
@ -45,6 +48,7 @@ void showError(Object error, [StackTrace? stackTrace]) {
error.toString().contains("Invalid source") || error.toString().contains("Invalid source") ||
error.toString().contains("UTF-16") || error.toString().contains("UTF-16") ||
error.toString().contains("HTTP request failed") || error.toString().contains("HTTP request failed") ||
error.toString().contains("'_nextFrame != null': is not true.") ||
error.toString().contains("Invalid image data")) { error.toString().contains("Invalid image data")) {
return; return;
} }
@ -62,7 +66,7 @@ void showError(Object error, [StackTrace? stackTrace]) {
} }
} }
void main() async { void main(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
MediaKit.ensureInitialized(); MediaKit.ensureInitialized();
@ -77,11 +81,14 @@ void main() async {
await windowManager.setMinimumSize(Size.square(500)); await windowManager.setMinimumSize(Size.square(500));
} }
isInBackground = args.contains("--unifiedpush-bg");
LicenseRegistry.addLicense(() => Stream.fromIterable(fontLicenses)); LicenseRegistry.addLicense(() => Stream.fromIterable(fontLicenses));
FlutterError.onError = (FlutterErrorDetails details) => FlutterError.onError = (FlutterErrorDetails details) =>
showError(details.exception.toString(), details.stack); showError(details.exception.toString(), details.stack);
if (!isInBackground) {
runApp( runApp(
ProviderScope( ProviderScope(
retry: (_, _) => null, retry: (_, _) => null,
@ -90,12 +97,14 @@ void main() async {
// ignore: dead_code // ignore: dead_code
if (false && kDebugMode) Logger(), if (false && kDebugMode) Logger(),
], ],
child: const App(), child: App(isInBackground),
), ),
); );
}
} }
class const App({super.key}) extends StatelessWidget { class const App(final bool isInBackground, {super.key})
extends StatelessWidget {
@override @override
Widget build(BuildContext context) => DynamicColorBuilder( Widget build(BuildContext context) => DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) => Consumer( builder: (lightDynamic, darkDynamic) => Consumer(
@ -138,6 +147,8 @@ class const App({super.key}) extends StatelessWidget {
IListConst([ IListConst([
SharedPrefsController.provider, SharedPrefsController.provider,
ClientController.provider, ClientController.provider,
NotificationsController.provider,
UnifiedPushController.provider,
]), ]),
), ),
) )

View file

@ -0,0 +1,25 @@
import "package:freezed_annotation/freezed_annotation.dart";
part "capabilities.freezed.dart";
part "capabilities.g.dart";
@Freezed(toJson: false, fromJson: false)
@JsonSerializable()
class const Capabilities({
@JsonKey(name: "org.matrix.msc4174.webpush") final WebPush? webpush,
}) with _$Capabilities {
Map<String, Object?> toJson() => _$CapabilitiesToJson(this);
factory Capabilities.fromJson(Map<String, Object?> json) =>
_$CapabilitiesFromJson(json);
}
@Freezed(toJson: false, fromJson: false)
@JsonSerializable()
class const WebPush({required final bool enabled, final String? vapid})
with _$WebPush {
Map<String, Object?> toJson() => _$WebPushToJson(this);
factory WebPush.fromJson(Map<String, Object?> json) =>
_$WebPushFromJson(json);
}

View file

@ -10,6 +10,7 @@ class const ClientState({
required final bool isLoggedIn, required final bool isLoggedIn,
required final bool isVerified, required final bool isVerified,
required final String? userId, required final String? userId,
required final String? deviceId,
required final String? homeserverUrl, required final String? homeserverUrl,
}) with _$ClientState { }) with _$ClientState {
Map<String, Object?> toJson() => _$ClientStateToJson(this); Map<String, Object?> toJson() => _$ClientStateToJson(this);

View file

@ -1,4 +1,4 @@
import "package:freezed_annotation/freezed_annotation.dart"; import "package:freezed_annotation/freezed_annotation.dart";
@JsonEnum(fieldRename: FieldRename.snake) @JsonEnum(fieldRename: .snake)
enum JoinRule { public, knock, invite, private, restricted, knockRestricted } enum JoinRule { public, knock, invite, private, restricted, knockRestricted }

View file

@ -1,4 +1 @@
import "package:freezed_annotation/freezed_annotation.dart";
@JsonEnum()
enum MembershipAction { ban, kick, unban, invite } enum MembershipAction { ban, kick, unban, invite }

View file

@ -1,4 +1 @@
import "package:freezed_annotation/freezed_annotation.dart";
@JsonEnum()
enum MembershipStatus { leave, invite, ban, join, knock } enum MembershipStatus { leave, invite, ban, join, knock }

View file

@ -9,7 +9,7 @@ part "profile_response.g.dart";
@JsonSerializable() @JsonSerializable()
class const ProfileResponse({ class const ProfileResponse({
@JsonKey(fromJson: Profile.fromJson) required final Profile profile, @JsonKey(fromJson: Profile.fromJson) required final Profile profile,
final Bio? bio, required final Bio? bio,
}) with _$ProfileResponse { }) with _$ProfileResponse {
Map<String, Object?> toJson() => _$ProfileResponseToJson(this); Map<String, Object?> toJson() => _$ProfileResponseToJson(this);

View file

@ -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<String, Object?> toJson() => _$DeregisterPusherRequestToJson(this);
}

View file

@ -0,0 +1,56 @@
import "package:freezed_annotation/freezed_annotation.dart";
part "register_pusher.freezed.dart";
part "register_pusher.g.dart";
@Freezed(toJson: false, fromJson: false)
@JsonSerializable()
class const RegisterPusherRequest({
required final String appDisplayName,
required final String appId,
final bool append = false,
required final PusherData data,
required final String deviceDisplayName,
required final PusherKind kind,
required final String lang,
/// TODO: What does this do?
final String? profileTag,
@JsonKey(name: "pushkey") required final String pushKey,
}) with _$RegisterPusherRequest {
Map<String, Object?> toJson() => _$RegisterPusherRequestToJson(this);
}
@freezed
sealed class PusherData with _$PusherData {
const factory PusherData.http({
required Uri url,
@Default(PushFormat.eventIdOnly) PushFormat format,
}) = HttpPusherData;
const factory PusherData.webPush({
required Uri url,
@Default(PushFormat.eventIdOnly) PushFormat format,
/// `data.auth`: RFC8291 authentication secret.
required String auth,
}) = WebPushPusherData;
factory PusherData.fromJson(Map<String, Object?> json) =>
_$PusherDataFromJson(json);
}
@JsonEnum(fieldRename: .snake)
enum PushFormat {
@JsonValue(null)
all,
eventIdOnly,
}
enum PusherKind {
http,
email,
@JsonValue("org.matrix.msc4174.webpush")
webPush,
}

View file

@ -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(),
);
}
}

View file

@ -39,7 +39,7 @@ final class const Appbar({
: (_) => windowManager.startDragging(), : (_) => windowManager.startDragging(),
), ),
child: AppBar( child: AppBar(
leading: InkWell(onTap: onTap, child: leading), leading: leading == null ? null : InkWell(onTap: onTap, child: leading),
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
scrolledUnderElevation: scrolledUnderElevation, scrolledUnderElevation: scrolledUnderElevation,
actionsPadding: const .symmetric(horizontal: 8), actionsPadding: const .symmetric(horizontal: 8),

View file

@ -31,15 +31,9 @@ class const UrlPreview(final Uri link, {super.key}) extends ConsumerWidget {
Text( Text(
preview.title!, preview.title!,
style: Theme.of(context).textTheme.titleLarge, style: Theme.of(context).textTheme.titleLarge,
maxLines: 3,
overflow: .ellipsis,
), ),
if (preview.description != null) ...[ if (preview.description != null) ...[
Text( Text(preview.description!),
preview.description!,
maxLines: 20,
overflow: .ellipsis,
),
SizedBox(height: 4), SizedBox(height: 4),
], ],
if (preview.imageUrl != null) if (preview.imageUrl != null)

View file

@ -207,14 +207,15 @@ final class const UserBottomSheet(
targetUser: userId, targetUser: userId,
), ),
), ),
)) ) &&
member.status == .join ||
member.status == .invite)
Padding( Padding(
padding: .only(top: 4), padding: .only(top: 4),
child: Row( child: Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
spacing: 8, spacing: 8,
children: [ children: [
if (member.status == .join || member.status == .invite)
M3EButton.icon( M3EButton.icon(
onPressed: () => showMembershipDialog(.kick), onPressed: () => showMembershipDialog(.kick),
shape: .square, shape: .square,

View file

@ -13,6 +13,7 @@
#include <media_kit_video/media_kit_video_plugin.h> #include <media_kit_video/media_kit_video_plugin.h>
#include <screen_retriever_linux/screen_retriever_linux_plugin.h> #include <screen_retriever_linux/screen_retriever_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
#include <webcrypto/webcrypto_plugin.h>
#include <window_manager/window_manager_plugin.h> #include <window_manager/window_manager_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
@ -37,6 +38,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 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 = g_autoptr(FlPluginRegistrar) window_manager_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin");
window_manager_plugin_register_with_registrar(window_manager_registrar); window_manager_plugin_register_with_registrar(window_manager_registrar);

View file

@ -10,6 +10,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
media_kit_video media_kit_video
screen_retriever_linux screen_retriever_linux
url_launcher_linux url_launcher_linux
webcrypto
window_manager window_manager
) )

View file

@ -45,7 +45,6 @@ pkgs.mkShell {
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_HOME}/build-tools/36.0.0/aapt2"; GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_HOME}/build-tools/36.0.0/aapt2";
} }
// lib.optionalAttrs pkgs.stdenv.isLinux { // lib.optionalAttrs pkgs.stdenv.isLinux {
CPATH = lib.makeSearchPath "include" [ pkgs.glibc.dev ];
LD_LIBRARY_PATH = "./build/native_assets/linux:${lib.makeLibraryPath [ pkgs.zlib ]}"; LD_LIBRARY_PATH = "./build/native_assets/linux:${lib.makeLibraryPath [ pkgs.zlib ]}";
}; };
} }

View file

@ -17,7 +17,7 @@ buildGoModule (
src = "${src}/gomuks"; src = "${src}/gomuks";
vendorHash = "sha256-9CS/lAVT8gLWACLomwcEa8YNlAXvBOskPPx4zERg0wQ="; vendorHash = "sha256-C03ss88QAnmZu+XxoHhc1M/261xFl8hR/pzLbU37Qe8=";
buildPhase = '' buildPhase = ''
runHook preBuild runHook preBuild

View file

@ -1,5 +1,6 @@
#include "my_application.h" #include "my_application.h"
#include <cstdlib>
#include <flutter_linux/flutter_linux.h> #include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11 #ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h> #include <gdk/gdkx.h>
@ -72,6 +73,7 @@ static void my_application_activate(GApplication* application) {
gdk_rgba_parse(&background_color, "#000000"); gdk_rgba_parse(&background_color, "#000000");
fl_view_set_background_color(view, &background_color); fl_view_set_background_color(view, &background_color);
gtk_widget_show(GTK_WIDGET(view)); gtk_widget_show(GTK_WIDGET(view));
if (std::getenv("FLUTTER_HEADLESS")) gtk_widget_hide(GTK_WIDGET(window));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
// Show the window when Flutter renders. // Show the window when Flutter renders.

View file

@ -8,6 +8,7 @@ import Foundation
import app_links import app_links
import dynamic_color import dynamic_color
import file_selector_macos import file_selector_macos
import flutter_local_notifications
import media_kit_libs_macos_video import media_kit_libs_macos_video
import media_kit_video import media_kit_video
import package_info_plus import package_info_plus
@ -15,12 +16,14 @@ import screen_retriever_macos
import shared_preferences_foundation import shared_preferences_foundation
import url_launcher_macos import url_launcher_macos
import wakelock_plus import wakelock_plus
import webcrypto
import window_manager import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
@ -28,5 +31,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
WebcryptoPlugin.register(with: registry.registrar(forPlugin: "WebcryptoPlugin"))
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
} }

View file

@ -77,10 +77,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
sha256: be169cf6ac481e052c4538715d88841d567150dfe1df38aaec76461a4e7b39f2 sha256: ace891da0862b0e4cabbb064ee3fd87b2728b898949fdb366d83fe98342c9f19
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.0" version: "4.2.0"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -253,10 +253,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: cross_file name: cross_file
sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51" sha256: f141ea4f277af142a0356955707f6556f37b03947d39d55585981a06ca437bd6
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.5+4" version: "0.3.5+5"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
@ -277,10 +277,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: cupertino_ui name: cupertino_ui
sha256: "7ed8ce4159d342eec4c65f4ea6eec57adaf9365404378541f38efc1da20a5b3d" sha256: "2137c0d41f3b62cc4d3d2495e6c354bd6b6133cd21c6bb155736cc31dbd49a11"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.0.1"
dart_style: dart_style:
dependency: transitive dependency: transitive
description: description:
@ -511,6 +511,46 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.0" version: "6.0.0"
flutter_local_notifications:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: "1447ba911c60f2ba3f25dae1af151ec187162566b0f57e37771bf0b400f013ad"
url: "https://pub.dev"
source: hosted
version: "22.3.0"
flutter_local_notifications_linux:
dependency: "direct main"
description:
name: flutter_local_notifications_linux
sha256: "9ca97e63776f29ab1b955725c09999fc2c150523269db150c39274f2a43c5a8b"
url: "https://pub.dev"
source: hosted
version: "8.0.1"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
sha256: "43c3761d916c9bd3d5c7ebbc44d82f4990329840c0c5d62ad5260cc1b5d399bd"
url: "https://pub.dev"
source: hosted
version: "12.2.0"
flutter_local_notifications_web:
dependency: transitive
description:
name: flutter_local_notifications_web
sha256: "516afaf97a2d1e67a036c6617321b00d205d72f7a67b6eccf936cd565f985878"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
flutter_local_notifications_windows:
dependency: transitive
description:
name: flutter_local_notifications_windows
sha256: "6f43bdd03b171b7a90f22647506fea33e2bb12294b7c7c7a3d690e960a382945"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
flutter_localizations: flutter_localizations:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -891,10 +931,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: material_ui name: material_ui
sha256: "4f3f38b9953df0a87d6bf5f21880029f77c47048487d5339410c39936be4683b" sha256: "7ba1ca315d50a004791dbab290417c52a61466d56db2822fe3c5c2994903110c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.1" version: "1.1.0"
measure_size: measure_size:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1501,6 +1541,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.7.1" version: "3.7.1"
timezone:
dependency: transitive
description:
name: timezone
sha256: "981d1020d6ef8fe1e7b3de5054e5b25579ae7c403d7734adc508ffc47668e9cb"
url: "https://pub.dev"
source: hosted
version: "0.11.1"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -1509,6 +1557,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
unifiedpush:
dependency: "direct main"
description:
name: unifiedpush
sha256: "8ed9767f750a1dc6159a77e2171641d0cb825dc87682d1ce1b8618689b79f58e"
url: "https://pub.dev"
source: hosted
version: "6.2.0"
unifiedpush_android:
dependency: transitive
description:
name: unifiedpush_android
sha256: a902e505e91c8e28b54b379bb27f255f66df409274a97af24d6719f12f0d13dc
url: "https://pub.dev"
source: hosted
version: "3.5.0"
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: "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: "1.0.0"
universal_platform: universal_platform:
dependency: transitive dependency: transitive
description: description:
@ -1685,6 +1781,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "3.0.3"
webcrypto:
dependency: "direct dev"
description:
name: webcrypto
sha256: "6b43001c4110856ff7fa5e5e65e7b2d44bec1d8b54a4d84d5fa2c7622267c5c1"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
webkit_inspection_protocol: webkit_inspection_protocol:
dependency: transitive dependency: transitive
description: description:
@ -1693,6 +1797,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.2.1"
webpush_encryption:
dependency: transitive
description:
name: webpush_encryption
sha256: "5b83272b91acda6ae515fcd980c94f06bf413702282497c5a68f5dfc64fed27f"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
win32: win32:
dependency: transitive dependency: transitive
description: description:

View file

@ -52,7 +52,7 @@ dependencies:
dynamic_polls: 0.0.8 dynamic_polls: 0.0.8
flutter_hooks: 0.21.3+1 flutter_hooks: 0.21.3+1
ffi: 2.2.0 ffi: 2.2.0
hooks: ^2.2.0 hooks: 2.2.0
code_assets: 1.2.1 code_assets: 1.2.1
ffigen: 21.0.0 ffigen: 21.0.0
timeago: 3.7.1 timeago: 3.7.1
@ -78,7 +78,11 @@ dependencies:
package_info_plus: 10.2.1 package_info_plus: 10.2.1
app_links: 7.2.1 app_links: 7.2.1
file_selector: 1.1.0 file_selector: 1.1.0
material_ui: 1.0.1 material_ui: 1.1.0
unifiedpush: 6.2.0
unifiedpush_storage_shared_preferences: 1.0.0
flutter_local_notifications: 22.3.0
flutter_local_notifications_linux: 8.0.1
dev_dependencies: dev_dependencies:
build_runner: 2.16.0 build_runner: 2.16.0
@ -87,6 +91,8 @@ dev_dependencies:
riverpod_lint: 3.1.8 riverpod_lint: 3.1.8
flutter_launcher_icons: 0.14.4 flutter_launcher_icons: 0.14.4
json_serializable: 6.14.1 json_serializable: 6.14.1
# Transitive dep but needed for commands
webcrypto: any
flutter_launcher_icons: flutter_launcher_icons:
image_path: assets/icon.png image_path: assets/icon.png

View file

@ -13,6 +13,7 @@
#include <media_kit_video/media_kit_video_plugin_c_api.h> #include <media_kit_video/media_kit_video_plugin_c_api.h>
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h> #include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
#include <webcrypto/webcrypto_plugin.h>
#include <window_manager/window_manager_plugin.h> #include <window_manager/window_manager_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
@ -30,6 +31,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows")); registry->GetRegistrarForPlugin("UrlLauncherWindows"));
WebcryptoPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("WebcryptoPlugin"));
WindowManagerPluginRegisterWithRegistrar( WindowManagerPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("WindowManagerPlugin")); registry->GetRegistrarForPlugin("WindowManagerPlugin"));
} }

View file

@ -10,10 +10,12 @@ list(APPEND FLUTTER_PLUGIN_LIST
media_kit_video media_kit_video
screen_retriever_windows screen_retriever_windows
url_launcher_windows url_launcher_windows
webcrypto
window_manager window_manager
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
flutter_local_notifications_windows
) )
set(PLUGIN_BUNDLED_LIBRARIES) set(PLUGIN_BUNDLED_LIBRARIES)