mirror of
https://gitlab.com/mysocialportal/relatica
synced 2024-10-18 11:13:31 +00:00
Initial implementation of MediaKit for desktop platforms
This commit is contained in:
parent
97d2b7ed7e
commit
df77a23452
13 changed files with 326 additions and 94 deletions
127
lib/controls/audio_video/av_control.dart
Normal file
127
lib/controls/audio_video/av_control.dart
Normal file
|
@ -0,0 +1,127 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:relatica/controls/audio_video/media_kit_av_control.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../globals.dart';
|
||||
import '../../services/setting_service.dart';
|
||||
import '../../utils/snackbar_builder.dart';
|
||||
import 'video_player_lib_av_control.dart';
|
||||
|
||||
final _shownVideos = <String>{};
|
||||
|
||||
final _useVideoPlayer = kIsWeb || Platform.isAndroid || Platform.isIOS;
|
||||
final _useMediaKit = Platform.isWindows || Platform.isMacOS || Platform.isLinux;
|
||||
|
||||
class AVControl extends StatefulWidget {
|
||||
final String videoUrl;
|
||||
final String description;
|
||||
final double width;
|
||||
final double height;
|
||||
|
||||
const AVControl({
|
||||
super.key,
|
||||
required this.videoUrl,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.description,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AVControl> createState() => _AVControlState();
|
||||
}
|
||||
|
||||
class _AVControlState extends State<AVControl> {
|
||||
void showVideo() {
|
||||
_shownVideos.add(widget.videoUrl);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final shown = !context.watch<SettingsService>().lowBandwidthMode ||
|
||||
_shownVideos.contains(widget.videoUrl);
|
||||
|
||||
final placeHolderBox = SizedBox(
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
child: const Card(
|
||||
color: Colors.black12,
|
||||
shape: RoundedRectangleBorder(),
|
||||
child: Icon(Icons.movie)),
|
||||
);
|
||||
|
||||
if (!shown) {
|
||||
return GestureDetector(
|
||||
onTap: showVideo,
|
||||
child: placeHolderBox,
|
||||
);
|
||||
}
|
||||
|
||||
if (_useVideoPlayer) {
|
||||
return VideoPlayerLibAvControl(
|
||||
videoUrl: widget.videoUrl,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
);
|
||||
}
|
||||
|
||||
if (_useMediaKit) {
|
||||
return MediaKitAvControl(
|
||||
videoUrl: widget.videoUrl,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
);
|
||||
}
|
||||
|
||||
return buildStandinWidget(context);
|
||||
}
|
||||
|
||||
Widget buildStandinWidget(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
final confirm = await showYesNoDialog(
|
||||
context, 'Open Link in external app? ${widget.videoUrl}');
|
||||
if (confirm != true) {
|
||||
return;
|
||||
}
|
||||
if (await canLaunchUrl(Uri.parse(widget.videoUrl))) {
|
||||
if (context.mounted) {
|
||||
buildSnackbar(
|
||||
context,
|
||||
'Attempting to launch video: ${widget.videoUrl}',
|
||||
);
|
||||
}
|
||||
await launchUrl(Uri.parse(widget.videoUrl));
|
||||
} else {
|
||||
if (context.mounted) {
|
||||
buildSnackbar(
|
||||
context, 'Unable to launch video: ${widget.videoUrl}');
|
||||
}
|
||||
}
|
||||
},
|
||||
child: SizedBox(
|
||||
height: widget.height,
|
||||
width: widget.width,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
widget.description.isNotEmpty
|
||||
? widget.description
|
||||
: 'Video: ${widget.videoUrl}',
|
||||
softWrap: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
108
lib/controls/audio_video/media_kit_av_control.dart
Normal file
108
lib/controls/audio_video/media_kit_av_control.dart
Normal file
|
@ -0,0 +1,108 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:media_kit_video/media_kit_video.dart';
|
||||
|
||||
class MediaKitAvControl extends StatefulWidget {
|
||||
final String videoUrl;
|
||||
final double width;
|
||||
final double height;
|
||||
|
||||
const MediaKitAvControl({
|
||||
super.key,
|
||||
required this.videoUrl,
|
||||
required this.width,
|
||||
required this.height,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MediaKitAvControl> createState() => _MediaKitAvControlState();
|
||||
}
|
||||
|
||||
class _MediaKitAvControlState extends State<MediaKitAvControl> {
|
||||
static final _logger = Logger('$MediaKitAvControl');
|
||||
final player = Player();
|
||||
VideoController? controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Future.microtask(() async {
|
||||
_logger.info('initializing');
|
||||
controller = await VideoController.create(player.handle);
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {});
|
||||
_logger.info('opening player');
|
||||
await player.open(Media(widget.videoUrl), play: false);
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {});
|
||||
_logger.info('done');
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
Future.microtask(() async {
|
||||
await controller?.dispose();
|
||||
await player.dispose();
|
||||
});
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void deactivate() {
|
||||
player.pause();
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
void toggleVideoPlay() async {
|
||||
player.playOrPause();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void resetPlay() async {
|
||||
await player.pause();
|
||||
await player.seek(Duration.zero);
|
||||
await player.play();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
print('Building MediaKit Control');
|
||||
if (controller == null) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: toggleVideoPlay,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Video(
|
||||
controller: controller,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: player.state.playing
|
||||
? const Icon(Icons.pause)
|
||||
: const Icon(Icons.play_arrow),
|
||||
onPressed: toggleVideoPlay,
|
||||
),
|
||||
IconButton(onPressed: resetPlay, icon: const Icon(Icons.replay)),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,17 +1,14 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
import '../services/setting_service.dart';
|
||||
|
||||
final _shownVideos = <String>{};
|
||||
|
||||
class VideoControl extends StatefulWidget {
|
||||
class VideoPlayerLibAvControl extends StatefulWidget {
|
||||
final String videoUrl;
|
||||
final double width;
|
||||
final double height;
|
||||
|
||||
const VideoControl({
|
||||
const VideoPlayerLibAvControl({
|
||||
super.key,
|
||||
required this.videoUrl,
|
||||
required this.width,
|
||||
|
@ -19,14 +16,16 @@ class VideoControl extends StatefulWidget {
|
|||
});
|
||||
|
||||
@override
|
||||
State<VideoControl> createState() => _VideoControlState();
|
||||
State<VideoPlayerLibAvControl> createState() =>
|
||||
_VideoPlayerLibAvControlState();
|
||||
}
|
||||
|
||||
class _VideoControlState extends State<VideoControl> {
|
||||
class _VideoPlayerLibAvControlState extends State<VideoPlayerLibAvControl> {
|
||||
late final VideoPlayerController videoPlayerController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
videoPlayerController = VideoPlayerController.network(widget.videoUrl)
|
||||
..initialize().then((_) {
|
||||
setState(() {});
|
||||
|
@ -35,8 +34,8 @@ class _VideoControlState extends State<VideoControl> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
videoPlayerController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -45,11 +44,6 @@ class _VideoControlState extends State<VideoControl> {
|
|||
super.deactivate();
|
||||
}
|
||||
|
||||
void showVideo() {
|
||||
_shownVideos.add(widget.videoUrl);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void toggleVideoPlay() {
|
||||
videoPlayerController.value.isPlaying
|
||||
? videoPlayerController.pause()
|
||||
|
@ -59,34 +53,25 @@ class _VideoControlState extends State<VideoControl> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final shown = !context.watch<SettingsService>().lowBandwidthMode ||
|
||||
_shownVideos.contains(widget.videoUrl);
|
||||
final size = videoPlayerController.value.size;
|
||||
final horizontalScale = size.width / widget.width;
|
||||
final verticalScale = size.height / widget.height;
|
||||
final scaling = min(horizontalScale, verticalScale);
|
||||
final videoWidth = scaling * size.width;
|
||||
final videoHeight = scaling * size.height;
|
||||
|
||||
final placeHolderBox = SizedBox(
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
child: const Card(
|
||||
if (!videoPlayerController.value.isInitialized) {
|
||||
return SizedBox(
|
||||
width: videoWidth,
|
||||
height: videoHeight,
|
||||
child: const Card(
|
||||
color: Colors.black12,
|
||||
shape: RoundedRectangleBorder(),
|
||||
child: Icon(Icons.movie)),
|
||||
);
|
||||
|
||||
if (!shown) {
|
||||
return GestureDetector(
|
||||
onTap: showVideo,
|
||||
child: placeHolderBox,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_shownVideos.add(widget.videoUrl);
|
||||
if (!videoPlayerController.value.isInitialized) {
|
||||
return placeHolderBox;
|
||||
}
|
||||
final size = videoPlayerController.value.size;
|
||||
final videoWidth = size.width <= widget.width ? size.width : widget.width;
|
||||
final videoHeight = size.width <= widget.width
|
||||
? size.height
|
||||
: size.height * videoWidth / size.width;
|
||||
return GestureDetector(
|
||||
onTap: toggleVideoPlay,
|
||||
child: Column(
|
|
@ -1,13 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../globals.dart';
|
||||
import '../models/attachment_media_type_enum.dart';
|
||||
import '../models/media_attachment.dart';
|
||||
import '../screens/image_viewer_screen.dart';
|
||||
import '../utils/snackbar_builder.dart';
|
||||
import 'audio_video/av_control.dart';
|
||||
import 'image_control.dart';
|
||||
import 'video_control.dart';
|
||||
|
||||
class MediaAttachmentViewerControl extends StatefulWidget {
|
||||
final List<MediaAttachment> attachments;
|
||||
|
@ -32,54 +29,12 @@ class _MediaAttachmentViewerControlState
|
|||
const width = 250.0;
|
||||
const height = 250.0;
|
||||
if (item.explicitType == AttachmentMediaType.video) {
|
||||
if (useVideoPlayer) {
|
||||
return VideoControl(
|
||||
videoUrl: item.uri.toString(),
|
||||
width: width,
|
||||
height: height,
|
||||
);
|
||||
}
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
final confirm = await showYesNoDialog(
|
||||
context, 'Open Video Link in external app? ${item.uri}');
|
||||
if (confirm != true) {
|
||||
return;
|
||||
}
|
||||
if (await canLaunchUrl(item.uri)) {
|
||||
if (mounted) {
|
||||
buildSnackbar(
|
||||
context,
|
||||
'Attempting to launch video: ${item.uri}',
|
||||
);
|
||||
}
|
||||
await launchUrl(item.uri);
|
||||
} else {
|
||||
if (mounted) {
|
||||
buildSnackbar(context, 'Unable to launch video: ${item.uri}');
|
||||
}
|
||||
}
|
||||
},
|
||||
child: SizedBox(
|
||||
height: height,
|
||||
width: width,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
item.description.isNotEmpty
|
||||
? item.description
|
||||
: 'Video: ${item.uri}',
|
||||
softWrap: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
return AVControl(
|
||||
videoUrl: item.uri.toString(),
|
||||
width: width,
|
||||
height: height,
|
||||
description: item.description,
|
||||
);
|
||||
}
|
||||
if (item.explicitType != AttachmentMediaType.image) {
|
||||
return Text('${item.explicitType}: ${item.uri}');
|
||||
|
|
|
@ -155,7 +155,7 @@ class _StatusControlState extends State<FlattenedTreeEntryControl> {
|
|||
return const SizedBox();
|
||||
}
|
||||
return SizedBox(
|
||||
height: 250.0,
|
||||
height: 300.0,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (context, index) {
|
||||
|
|
|
@ -15,9 +15,7 @@ final platformHasCamera = Platform.isIOS || Platform.isAndroid;
|
|||
|
||||
final useImagePicker = kIsWeb || Platform.isAndroid || Platform.isIOS;
|
||||
|
||||
final useVideoPlayer = kIsWeb || Platform.isAndroid || Platform.isIOS;
|
||||
|
||||
final usePhpDebugging = true;
|
||||
const usePhpDebugging = true;
|
||||
|
||||
Future<bool?> showConfirmDialog(BuildContext context, String caption) {
|
||||
return showDialog<bool>(
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include <desktop_window/desktop_window_plugin.h>
|
||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
||||
#include <media_kit_video/media_kit_video_plugin.h>
|
||||
#include <objectbox_flutter_libs/objectbox_flutter_libs_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
|
@ -18,6 +20,12 @@ 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) media_kit_libs_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
|
||||
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) media_kit_video_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitVideoPlugin");
|
||||
media_kit_video_plugin_register_with_registrar(media_kit_video_registrar);
|
||||
g_autoptr(FlPluginRegistrar) objectbox_flutter_libs_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "ObjectboxFlutterLibsPlugin");
|
||||
objectbox_flutter_libs_plugin_register_with_registrar(objectbox_flutter_libs_registrar);
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
desktop_window
|
||||
flutter_secure_storage_linux
|
||||
media_kit_libs_linux
|
||||
media_kit_video
|
||||
objectbox_flutter_libs
|
||||
url_launcher_linux
|
||||
)
|
||||
|
|
|
@ -9,6 +9,7 @@ import desktop_window
|
|||
import device_info_plus
|
||||
import flutter_secure_storage_macos
|
||||
import flutter_web_auth_2
|
||||
import media_kit_video
|
||||
import objectbox_flutter_libs
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
|
@ -20,6 +21,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
FlutterWebAuth2Plugin.register(with: registry.registrar(forPlugin: "FlutterWebAuth2Plugin"))
|
||||
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
|
||||
ObjectboxFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "ObjectboxFlutterLibsPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
|
|
42
pubspec.lock
42
pubspec.lock
|
@ -665,6 +665,30 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
media_kit:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: media_kit
|
||||
sha256: baaaaa025bffba908e9b4a1c824181a8b3ce44bbf77c82be1728427dbaa848d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.2"
|
||||
media_kit_libs_linux:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: media_kit_libs_linux
|
||||
sha256: "7310b17dd2abc2e7363f78a273086445c2216c7b6dfb60933ca3814031d03814"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
media_kit_video:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: media_kit_video
|
||||
sha256: f9987d92182f42852fa77e5ca98d11a91482b9f45f7a559ca3453f258026041b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.2"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -905,6 +929,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.27.7"
|
||||
safe_local_storage:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: safe_local_storage
|
||||
sha256: ede4eb6cb7d88a116b3d3bf1df70790b9e2038bc37cb19112e381217c74d9440
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
scrollable_positioned_list:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1118,6 +1150,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
uri_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uri_parser
|
||||
sha256: "6543c9fd86d2862fac55d800a43e67c0dcd1a41677cb69c2f8edfe73bbcf1835"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1288,4 +1328,4 @@ packages:
|
|||
version: "3.1.1"
|
||||
sdks:
|
||||
dart: ">=2.19.0 <3.0.0"
|
||||
flutter: ">=3.7.0-0"
|
||||
flutter: ">=3.7.0"
|
||||
|
|
|
@ -30,6 +30,9 @@ dependencies:
|
|||
image_picker: ^0.8.6
|
||||
logging: ^1.1.0
|
||||
markdown: ^7.0.1
|
||||
media_kit: ^0.0.2
|
||||
media_kit_video: ^0.0.2
|
||||
media_kit_libs_linux: ^1.0.1
|
||||
metadata_fetch: ^0.4.1
|
||||
multi_trigger_autocomplete: ^0.1.1
|
||||
network_to_file_image: ^4.0.1
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <desktop_window/desktop_window_plugin.h>
|
||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
#include <media_kit_video/media_kit_video_plugin_c_api.h>
|
||||
#include <objectbox_flutter_libs/objectbox_flutter_libs_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
|
@ -16,6 +17,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||
registry->GetRegistrarForPlugin("DesktopWindowPlugin"));
|
||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||
MediaKitVideoPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi"));
|
||||
ObjectboxFlutterLibsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ObjectboxFlutterLibsPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
desktop_window
|
||||
flutter_secure_storage_windows
|
||||
media_kit_video
|
||||
objectbox_flutter_libs
|
||||
url_launcher_windows
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue