feat: discord rpc for macOS, windows-arm64 and linux-arm64 (#1713)

* feat: add discord rpc support for macos, windows arm64 and linux arm64

* chore: discord rpc not clearing activity after close/setting rpc to false

* chore: add migration script to move from files from macos sandbox to non-sandbox directories
This commit is contained in:
Kingkor Roy Tirtho 2024-07-14 18:58:47 +06:00 committed by GitHub
parent a6e13ffc08
commit 6a500731d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 230 additions and 136 deletions

View File

@ -1,10 +1,10 @@
import 'dart:async';
import 'package:dart_discord_rpc/dart_discord_rpc.dart';
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_discord_rpc/flutter_discord_rpc.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:hive/hive.dart';
@ -12,6 +12,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:local_notifier/local_notifier.dart';
import 'package:media_kit/media_kit.dart';
import 'package:metadata_god/metadata_god.dart';
import 'package:spotube/collections/env.dart';
import 'package:spotube/collections/initializers.dart';
import 'package:spotube/collections/routes.dart';
import 'package:spotube/collections/intents.dart';
@ -37,6 +38,7 @@ import 'package:spotube/services/logger/logger.dart';
import 'package:spotube/services/wm_tools/wm_tools.dart';
import 'package:spotube/themes/theme.dart';
import 'package:spotube/utils/migrations/hive.dart';
import 'package:spotube/utils/migrations/sandbox.dart';
import 'package:spotube/utils/platform.dart';
import 'package:system_theme/system_theme.dart';
import 'package:path_provider/path_provider.dart';
@ -67,6 +69,8 @@ Future<void> main(List<String> rawArgs) async {
MediaKit.ensureInitialized();
await migrateMacOsFromSandboxToNoSandbox();
// force High Refresh Rate on some Android devices (like One Plus)
if (kIsAndroid) {
await FlutterDisplayMode.setHighRefreshRate();
@ -82,8 +86,8 @@ Future<void> main(List<String> rawArgs) async {
MetadataGod.initialize();
}
if (kIsWindows || kIsLinux) {
DiscordRPC.initialize();
if (kIsDesktop) {
await FlutterDiscordRPC.initialize(Env.discordAppId);
}
await KVStoreService.initialize();
@ -108,6 +112,9 @@ Future<void> main(List<String> rawArgs) async {
overrides: [
databaseProvider.overrideWith((ref) => database),
],
observers: const [
AppLoggerProviderObserver(),
],
child: const Spotube(),
),
);

View File

@ -4,6 +4,7 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/collections/assets.gen.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/modules/connect/connect_device.dart';
import 'package:spotube/modules/home/sections/featured.dart';
import 'package:spotube/modules/home/sections/feed.dart';
@ -15,6 +16,7 @@ import 'package:spotube/modules/home/sections/recent.dart';
import 'package:spotube/components/titlebar/titlebar.dart';
import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/pages/settings/settings.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/utils/platform.dart';
import 'package:spotube/utils/service_utils.dart';
@ -26,6 +28,8 @@ class HomePage extends HookConsumerWidget {
Widget build(BuildContext context, ref) {
final controller = useScrollController();
final mediaQuery = MediaQuery.of(context);
final layoutMode =
ref.watch(userPreferencesProvider.select((s) => s.layoutMode));
return SafeArea(
bottom: false,
@ -34,7 +38,7 @@ class HomePage extends HookConsumerWidget {
body: CustomScrollView(
controller: controller,
slivers: [
if (mediaQuery.smAndDown)
if (mediaQuery.smAndDown || layoutMode == LayoutMode.compact)
SliverAppBar(
floating: true,
title: Assets.spotubeLogoPng.image(height: 45),

View File

@ -8,8 +8,6 @@ import 'package:spotube/components/adaptive/adaptive_select_tile.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/utils/platform.dart';
class SettingsDesktopSection extends HookConsumerWidget {
const SettingsDesktopSection({super.key});
@ -54,13 +52,12 @@ class SettingsDesktopSection extends HookConsumerWidget {
value: preferences.systemTitleBar,
onChanged: preferencesNotifier.setSystemTitleBar,
),
if (!kIsMacOS)
SwitchListTile(
secondary: const Icon(SpotubeIcons.discord),
title: Text(context.l10n.discord_rich_presence),
value: preferences.discordPresence,
onChanged: preferencesNotifier.setDiscordPresence,
),
SwitchListTile(
secondary: const Icon(SpotubeIcons.discord),
title: Text(context.l10n.discord_rich_presence),
value: preferences.discordPresence,
onChanged: preferencesNotifier.setDiscordPresence,
),
],
);
}

View File

@ -43,7 +43,7 @@ class AudioPlayerStreamListeners {
ScrobblerNotifier get scrobbler => ref.read(scrobblerProvider.notifier);
UserPreferences get preferences => ref.read(userPreferencesProvider);
Discord get discord => ref.read(discordProvider);
DiscordNotifier get discord => ref.read(discordProvider.notifier);
AudioPlayerState get audioPlayerState => ref.read(audioPlayerProvider);
PlaybackHistoryActions get history =>
ref.read(playbackHistoryActionsProvider);

View File

@ -1,67 +1,76 @@
import 'package:dart_discord_rpc/dart_discord_rpc.dart';
import 'package:flutter/foundation.dart';
import 'dart:async';
import 'package:flutter_discord_rpc/flutter_discord_rpc.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/env.dart';
import 'package:spotube/extensions/artist_simple.dart';
import 'package:spotube/provider/audio_player/audio_player.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/utils/platform.dart';
class Discord extends ChangeNotifier {
final DiscordRPC? discordRPC;
final bool isEnabled;
class DiscordNotifier extends AsyncNotifier<void> {
@override
FutureOr<void> build() async {
final enabled = ref.watch(
userPreferencesProvider.select((s) => s.discordPresence && kIsDesktop));
final playback = ref.read(audioPlayerProvider);
Discord(this.isEnabled)
: discordRPC = (kIsWindows || kIsLinux) && isEnabled
? DiscordRPC(applicationId: Env.discordAppId)
: null {
discordRPC?.start(autoRegister: true);
final subscription =
FlutterDiscordRPC.instance.isConnectedStream.listen((connected) async {
if (connected && playback.activeTrack != null) {
await updatePresence(playback.activeTrack!);
}
});
ref.onDispose(() async {
subscription.cancel();
await close();
await FlutterDiscordRPC.instance.dispose();
});
if (!enabled && FlutterDiscordRPC.instance.isConnected) {
await clear();
await close();
} else {
await FlutterDiscordRPC.instance.connect(autoRetry: true);
}
}
void updatePresence(Track track) {
clear();
Future<void> updatePresence(Track track) async {
await clear();
final artistNames = track.artists?.asString() ?? "";
discordRPC?.updatePresence(
DiscordPresence(
details: "Song: ${track.name} by $artistNames",
await FlutterDiscordRPC.instance.setActivity(
activity: RPCActivity(
details: "${track.name} by $artistNames",
state: "Vibing in Music",
startTimeStamp: DateTime.now().millisecondsSinceEpoch,
largeImageKey: "spotube-logo-foreground",
largeImageText: "Spotube",
smallImageKey: "spotube-logo-foreground",
smallImageText: "Spotube",
assets: const RPCAssets(
largeImage: "spotube-logo-foreground",
largeText: "Spotube",
smallImage: "spotube-logo-foreground",
smallText: "Spotube",
),
buttons: [
RPCButton(
label: "Listen on Spotify",
url: track.externalUrls?.spotify ??
"https://open.spotify.com/tracks/${track.id}",
),
],
timestamps: RPCTimestamps(
start: DateTime.now().millisecondsSinceEpoch,
),
),
);
}
void clear() {
discordRPC?.clearPresence();
Future<void> clear() async {
await FlutterDiscordRPC.instance.clearActivity();
}
void shutdown() {
discordRPC?.shutDown();
}
@override
void dispose() {
clear();
shutdown();
super.dispose();
Future<void> close() async {
await FlutterDiscordRPC.instance.disconnect();
}
}
final discordProvider = ChangeNotifierProvider(
(ref) {
final isEnabled =
ref.watch(userPreferencesProvider.select((s) => s.discordPresence));
final playback = ref.read(audioPlayerProvider);
final discord = Discord(isEnabled);
if (playback.activeTrack != null) {
discord.updatePresence(playback.activeTrack!);
}
return discord;
},
);
final discordProvider =
AsyncNotifierProvider<DiscordNotifier, void>(() => DiscordNotifier());

View File

@ -4,6 +4,7 @@ import 'dart:isolate';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:logger/logger.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
@ -88,3 +89,17 @@ class AppLogger {
}
}
}
class AppLoggerProviderObserver extends ProviderObserver {
const AppLoggerProviderObserver();
@override
void providerDidFail(
ProviderBase<Object?> provider,
Object error,
StackTrace stackTrace,
ProviderContainer container,
) {
AppLogger.reportError(error, stackTrace);
}
}

View File

@ -0,0 +1,58 @@
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:spotube/services/logger/logger.dart';
import 'package:spotube/utils/platform.dart';
/// Migrates sandbox files on macOS to non-sandbox directories
Future<void> migrateMacOsFromSandboxToNoSandbox() async {
if (!kIsMacOS) return;
try {
final sandboxApplicationSupportDir = Directory(
"/Users/${Platform.environment["USER"]}/Library/Containers/oss.krtirtho.spotube/Data/Library/Application Support/oss.krtirtho.spotube",
);
if (!await sandboxApplicationSupportDir.exists()) {
stdout.writeln("🔵 Sandbox directory not found, skipping migration");
return;
}
const fileExts = [".db", ".lock", ".hive"];
final supportDir = await getApplicationSupportDirectory()
..create(recursive: true);
final supportFiles = await supportDir.list().toList();
final oldSupportFiles = await sandboxApplicationSupportDir.list().toList();
if (oldSupportFiles.isEmpty) {
stdout.writeln(
"🔵 No files found in sandboxed directory, skipping migration",
);
return;
} else if (supportFiles.any(
(file) => file is File && fileExts.contains(extension(file.path)))) {
stdout.writeln(
"🔵 Non-sandbox directory is not empty, skipping migration",
);
return;
}
for (final oldSupportFile in oldSupportFiles) {
if (oldSupportFile is File &&
fileExts.contains(extension(oldSupportFile.path))) {
final newPath = join(supportDir.path, basename(oldSupportFile.path));
await oldSupportFile.copy(newPath);
}
}
stdout.writeln("✅ Migrated sandboxed files to non-sandboxed directory");
} catch (e, stack) {
stdout.writeln(
"❌ Error migrating sandboxed files to non-sandboxed directory",
);
AppLogger.reportError(e, stack);
}
}

View File

@ -6,7 +6,6 @@
#include "generated_plugin_registrant.h"
#include <dart_discord_rpc/dart_discord_rpc_plugin.h>
#include <desktop_webview_window/desktop_webview_window_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
@ -22,9 +21,6 @@
#include <window_manager/window_manager_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) dart_discord_rpc_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DartDiscordRpcPlugin");
dart_discord_rpc_plugin_register_with_registrar(dart_discord_rpc_registrar);
g_autoptr(FlPluginRegistrar) desktop_webview_window_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopWebviewWindowPlugin");
desktop_webview_window_plugin_register_with_registrar(desktop_webview_window_registrar);

View File

@ -3,7 +3,6 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
dart_discord_rpc
desktop_webview_window
file_selector_linux
flutter_secure_storage_linux
@ -20,6 +19,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
flutter_discord_rpc
media_kit_native_event_loop
metadata_god
)

View File

@ -14,6 +14,7 @@ PODS:
- FlutterMacOS
- file_selector_macos (0.0.1):
- FlutterMacOS
- flutter_discord_rpc (0.0.1)
- flutter_inappwebview_macos (0.0.1):
- FlutterMacOS
- OrderedSet (~> 5.0)
@ -41,14 +42,14 @@ PODS:
- sqflite (0.0.3):
- Flutter
- FlutterMacOS
- sqlite3 (3.46.0):
- sqlite3/common (= 3.46.0)
- sqlite3/common (3.46.0)
- sqlite3/fts5 (3.46.0):
- "sqlite3 (3.46.0+1)":
- "sqlite3/common (= 3.46.0+1)"
- "sqlite3/common (3.46.0+1)"
- "sqlite3/fts5 (3.46.0+1)":
- sqlite3/common
- sqlite3/perf-threadsafe (3.46.0):
- "sqlite3/perf-threadsafe (3.46.0+1)":
- sqlite3/common
- sqlite3/rtree (3.46.0):
- "sqlite3/rtree (3.46.0+1)":
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- FlutterMacOS
@ -75,6 +76,7 @@ DEPENDENCIES:
- desktop_webview_window (from `Flutter/ephemeral/.symlinks/plugins/desktop_webview_window/macos`)
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
- flutter_discord_rpc (from `Flutter/ephemeral/.symlinks/plugins/flutter_discord_rpc/macos`)
- flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`)
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
@ -114,6 +116,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
file_selector_macos:
:path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
flutter_discord_rpc:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_discord_rpc/macos
flutter_inappwebview_macos:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos
flutter_secure_storage_macos:
@ -159,6 +163,7 @@ SPEC CHECKSUMS:
desktop_webview_window: 89bb3d691f4c80314a10be312f4cd35db93a9d5a
device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9
flutter_discord_rpc: 53b006f68ef620a99fe1b3ba7e83513f3ae95b4c
flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
@ -172,7 +177,7 @@ SPEC CHECKSUMS:
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
sqlite3: 154b084339ede06960a5b3c8160066adc9176b7d
sqlite3: 292c3e1bfe89f64e51ea7fc7dab9182a017c8630
sqlite3_flutter_libs: 1be4459672f8168ded2d8667599b8e3ca5e72b83
system_theme: c7b9f6659a5caa26c9bc2284da096781e9a6fcbc
system_tray: e53c972838c69589ff2e77d6d3abfd71332f9e5d

View File

@ -1,18 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.assets.music.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
<dict>
<key>com.apple.security.app-sandbox</key>
<false />
<key>com.apple.security.assets.music.read-write</key>
<true />
<key>com.apple.security.files.downloads.read-write</key>
<true />
<key>com.apple.security.files.user-selected.read-write</key>
<true />
<key>com.apple.security.network.client</key>
<true />
<key>com.apple.security.network.server</key>
<true />
</dict>
</plist>

View File

@ -1,18 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.assets.music.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
<dict>
<key>com.apple.security.app-sandbox</key>
<false />
<key>com.apple.security.assets.music.read-write</key>
<true />
<key>com.apple.security.files.downloads.read-write</key>
<true />
<key>com.apple.security.files.user-selected.read-write</key>
<true />
<key>com.apple.security.network.client</key>
<true />
<key>com.apple.security.network.server</key>
<true />
</dict>
</plist>

View File

@ -1,18 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.assets.music.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
<dict>
<key>com.apple.security.app-sandbox</key>
<false />
<key>com.apple.security.assets.music.read-write</key>
<true />
<key>com.apple.security.files.downloads.read-write</key>
<true />
<key>com.apple.security.files.user-selected.read-write</key>
<true />
<key>com.apple.security.network.client</key>
<true />
<key>com.apple.security.network.server</key>
<true />
</dict>
</plist>

View File

@ -221,10 +221,10 @@ packages:
dependency: "direct dev"
description:
name: build_runner
sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7"
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
url: "https://pub.dev"
source: hosted
version: "2.4.11"
version: "2.4.9"
build_runner_core:
dependency: transitive
description:
@ -433,15 +433,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.2"
dart_discord_rpc:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: "4d05017838ebeadcdb832e1893fabad1506fddba"
url: "https://github.com/Tommypop2/dart_discord_rpc.git"
source: git
version: "0.0.3"
dart_mappable:
dependency: transitive
description:
@ -721,6 +712,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.3.1"
flutter_discord_rpc:
dependency: "direct main"
description:
name: flutter_discord_rpc
sha256: "290c0d91c8ef24c3acb84cb6fcc5c5fed652fe4871b59896c8d180d5eae5d647"
url: "https://pub.dev"
source: hosted
version: "0.1.0+1"
flutter_displaymode:
dependency: "direct main"
description:
@ -1791,6 +1790,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.0"
retry:
dependency: "direct main"
description:
name: retry
sha256: "822e118d5b3aafed083109c72d5f484c6dc66707885e07c0fbcb8b986bba7efc"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
riverpod:
dependency: "direct main"
description:
@ -2488,5 +2495,5 @@ packages:
source: hosted
version: "2.2.1"
sdks:
dart: ">=3.4.0 <4.0.0"
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.19.2"

View File

@ -100,9 +100,7 @@ dependencies:
very_good_infinite_list: ^0.7.1
gap: ^3.0.1
sliver_tools: ^0.2.12
dart_discord_rpc:
git:
url: https://github.com/Tommypop2/dart_discord_rpc.git
flutter_discord_rpc: ^0.1.0+1
html_unescape: ^2.0.0
wikipedia_api: ^0.1.0
skeletonizer: ^1.1.1
@ -130,6 +128,7 @@ dependencies:
sqlite3_flutter_libs: ^0.5.23
sqlite3: ^2.4.3
encrypt: ^5.0.3
retry: ^3.1.2
dev_dependencies:
build_runner: ^2.4.9

View File

@ -8,7 +8,6 @@
#include <app_links/app_links_plugin_c_api.h>
#include <bonsoir_windows/bonsoir_windows_plugin_c_api.h>
#include <dart_discord_rpc/dart_discord_rpc_plugin.h>
#include <desktop_webview_window/desktop_webview_window_plugin.h>
#include <file_selector_windows/file_selector_windows.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
@ -28,8 +27,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
BonsoirWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("BonsoirWindowsPluginCApi"));
DartDiscordRpcPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DartDiscordRpcPlugin"));
DesktopWebviewWindowPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DesktopWebviewWindowPlugin"));
FileSelectorWindowsRegisterWithRegistrar(

View File

@ -5,7 +5,6 @@
list(APPEND FLUTTER_PLUGIN_LIST
app_links
bonsoir_windows
dart_discord_rpc
desktop_webview_window
file_selector_windows
flutter_secure_storage_windows
@ -22,6 +21,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
flutter_discord_rpc
media_kit_native_event_loop
metadata_god
smtc_windows