diff --git a/android/app/build.gradle b/android/app/build.gradle index 96177047..b4afdf7f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -67,7 +67,7 @@ android { defaultConfig { applicationId "com.github.lamarios.clipious" - minSdkVersion 21 + minSdkVersion 24 targetSdkVersion 34 versionCode buildNumber versionName flutterVersionName diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 016ec939..84adf226 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ + super.videoId; @JsonKey(includeFromJson: false, includeToJson: false) - Future get mediaPath async { + Future get downloadPath async { Directory dir = await _getDownloadFolder(); - return "${dir.path}/$videoId.${audioOnly ? 'webm' : 'mp4'}"; + + return "${dir.path}/$videoId.${audioOnly ? 'webm' : 'webm'}"; + } + + /// Effective path for legacy reasons, old versions videos would be mp4, now it's all webm + @JsonKey(includeFromJson: false, includeToJson: false) + Future get effectivePath async { + Directory dir = await _getDownloadFolder(); + + if (audioOnly) { + return "${dir.path}/$videoId.webm"; + } else { + final path = "${dir.path}/$videoId.webm"; + if (!await File(path).exists()) { + return "${dir.path}/$videoId.mp4"; + } + + return path; + } } @JsonKey(includeFromJson: false, includeToJson: false) diff --git a/lib/downloads/states/download_manager.dart b/lib/downloads/states/download_manager.dart index 3a0c2fa3..4a9a21c7 100644 --- a/lib/downloads/states/download_manager.dart +++ b/lib/downloads/states/download_manager.dart @@ -3,14 +3,17 @@ import 'dart:io'; import 'package:bloc/bloc.dart'; import 'package:dio/dio.dart'; +import 'package:downloadsfolder/downloadsfolder.dart'; import 'package:easy_debounce/easy_throttle.dart'; +import 'package:ffmpeg_kit_flutter_full/ffmpeg_kit.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:invidious/downloads/models/downloaded_video.dart'; import 'package:invidious/extensions.dart'; import 'package:invidious/globals.dart'; import 'package:invidious/utils/models/image_object.dart'; -import 'package:invidious/videos/models/format_stream.dart'; import 'package:logging/logging.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as p; import '../../player/states/player.dart'; import '../../videos/models/adaptive_format.dart'; @@ -71,8 +74,9 @@ class DownloadManagerCubit extends Cubit { .toList()); } - onProgress(int count, int total, DownloadedVideo video) async { - if (count == total) { + onProgress(int count, int total, DownloadedVideo video, + {required int step, required int totalSteps}) async { + if (count == total && step == totalSteps) { var progresses = Map.from(state.downloadProgresses); var downloadProgress = progresses[video.videoId]; @@ -119,18 +123,23 @@ class DownloadManagerCubit extends Cubit { quality: quality); await db.upsertDownload(downloadedVideo); - String contentUrl; + String? videoUrl; if (!audioOnly) { - FormatStream stream = vid.formatStreams - .firstWhere((element) => element.resolution == quality); - contentUrl = stream.url; - } else { - AdaptiveFormat audio = vid.adaptiveFormats - .sortByReversed((e) => int.parse(e.bitrate ?? "0")) - .firstWhere((element) => element.type.contains("audio")); - contentUrl = audio.url; + // FormatStream stream = vid.formatStreams + // .firstWhere((element) => element.resolution == quality); + + final stream = vid.adaptiveFormats.firstWhere((element) => + element.encoding == 'vp9' && + element.qualityLabel == quality && + element.type.contains('video/webm')); + + videoUrl = stream.url; } + AdaptiveFormat audio = vid.adaptiveFormats + .sortByReversed((e) => int.parse(e.bitrate ?? "0")) + .firstWhere((element) => element.type.contains("audio")); + String audioUrl = audio.url; Dio dio = Dio(); CancelToken cancelToken = CancelToken(); @@ -152,22 +161,74 @@ class DownloadManagerCubit extends Cubit { } // download video - var videoPath = await downloadedVideo.mediaPath; + var mediaPath = await downloadedVideo.downloadPath; + + final tempDir = await getTemporaryDirectory(); + final audioPath = '${tempDir.path}/${videoId}_audio.webm'; + final videoPath = '${tempDir.path}/${videoId}_video.webm'; log.info( - "Downloading video ${vid.title}, audioOnly ? $audioOnly, quality: $quality (if not only audio) to path: $videoPath"); - dio - .download(contentUrl, videoPath, - onReceiveProgress: (count, total) => - onProgress(count, total, downloadedVideo), - cancelToken: cancelToken, - deleteOnError: true) - .catchError((err) { - onDownloadError(err, downloadedVideo); - return Response(requestOptions: RequestOptions()); - }); + "Downloading video ${vid.title}, audioOnly ? $audioOnly, quality: $quality to path: $tempDir"); + try { + await dio + .download(audioUrl, audioPath, + onReceiveProgress: (count, total) => onProgress( + count, total, downloadedVideo, + step: 1, totalSteps: audioOnly ? 2 : 3), + cancelToken: cancelToken, + deleteOnError: true) + .catchError((err) { + onDownloadError(err, downloadedVideo); + return Response(requestOptions: RequestOptions()); + }); + + if (videoUrl != null) { + await dio + .download(videoUrl, videoPath, + onReceiveProgress: (count, total) => onProgress( + count, + total, + downloadedVideo, + step: 2, + totalSteps: 3, + ), + cancelToken: cancelToken, + deleteOnError: true) + .catchError((err) { + onDownloadError(err, downloadedVideo); + return Response(requestOptions: RequestOptions()); + }); + } + + if (audioOnly) { + final audio = File(audioPath); + await audio.copy(mediaPath); + onProgress(1, 1, downloadedVideo, step: 2, totalSteps: 2); + return true; + } else { + final session = await FFmpegKit.execute( + '-y -i $videoPath -i $audioPath -c:v copy -c:a copy $mediaPath'); + + final returnCode = await session.getReturnCode(); + + onProgress(1, 1, downloadedVideo, step: 3, totalSteps: 3); + + return returnCode?.isValueSuccess() ?? false; + } + } catch (e) { + rethrow; + } finally { + final audio = File(audioPath); + final video = File(videoPath); + + if (await audio.exists()) { + await audio.delete(); + } - return true; + if (await video.exists()) { + await video.delete(); + } + } } } @@ -182,7 +243,7 @@ class DownloadManagerCubit extends Cubit { progresses.remove(vid.videoId); try { - String path = await vid.mediaPath; + String path = await vid.effectivePath; await File(path).delete(); } catch (e) { log.fine('File might not be available, that\'s ok'); @@ -210,7 +271,7 @@ class DownloadManagerCubit extends Cubit { "Failed to download video ${vid.title}, removing it", err.stackTrace); vid.downloadFailed = true; vid.downloadComplete = false; - onProgress(1, 1, vid); + onProgress(1, 1, vid, step: 1, totalSteps: 1); var progresses = Map.from(state.downloadProgresses); progresses.remove(vid.videoId); @@ -245,6 +306,24 @@ class DownloadManagerCubit extends Cubit { bool canPlayAll() => state.videos.where((element) => element.downloadComplete).isNotEmpty; + + Future copyToDownloadFolder(DownloadedVideo v) async { + final downloads = await getDownloadsDirectory(); + if (downloads != null) { + if (!await downloads.exists()) { + downloads.create(recursive: true); + } + final file = File(await v.effectivePath); + if (await file.exists()) { + final fileName = + '${v.title.replaceAll(RegExp(r'[^a-zA-Z0-9]'), '_').replaceAll(RegExp(r'_{2,}'), '')}_${v.videoId}${p.extension(file.path)}'; + + bool? success = await copyFileIntoDownloadFolder(file.path, fileName); + log.info( + "file ${file.path} copied to download folder as $fileName (success ?$success)"); + } + } + } } @freezed diff --git a/lib/downloads/views/components/downloaded_video.dart b/lib/downloads/views/components/downloaded_video.dart index 3393e0a6..215623eb 100644 --- a/lib/downloads/views/components/downloaded_video.dart +++ b/lib/downloads/views/components/downloaded_video.dart @@ -14,6 +14,61 @@ class DownloadedVideoView extends StatelessWidget { const DownloadedVideoView({super.key, required this.video}); + openVideoSheet(BuildContext context, DownloadedVideo v) { + var cubit = context.read(); + final locals = AppLocalizations.of(context)!; + showModalBottomSheet( + enableDrag: true, + showDragHandle: true, + context: context, + builder: (ctx) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: Wrap( + alignment: WrapAlignment.center, + runSpacing: 16, + spacing: 16, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton.filledTonal( + onPressed: () async { + Navigator.of(ctx).pop(); + await cubit.copyToDownloadFolder(v); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: + Text(locals.fileCopiedToDownloadFolder))); + } + }, + icon: const Icon(Icons.copy)), + Text(locals.copyToDownloadFolder) + ], + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton.filledTonal( + onPressed: () async { + Navigator.of(ctx).pop(); + await cubit.deleteVideo(v); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(locals.videoDeleted))); + } + }, + icon: const Icon(Icons.delete)), + Text(locals.delete) + ], + ) + ], + ), + ); + }, + ); + } + @override Widget build(BuildContext context) { var colors = Theme.of(context).colorScheme; @@ -69,7 +124,16 @@ class DownloadedVideoView extends StatelessWidget { ); }), ), - ) + ), + if (state.video?.downloadComplete ?? false) + IconButton( + onPressed: () => openVideoSheet(context, video), + icon: Icon( + Icons.more_vert, + color: colors.secondary, + ), + visualDensity: VisualDensity.compact, + ) ], ), ), diff --git a/lib/downloads/views/screens/download_manager.dart b/lib/downloads/views/screens/download_manager.dart index 59fe9327..5fa655e6 100644 --- a/lib/downloads/views/screens/download_manager.dart +++ b/lib/downloads/views/screens/download_manager.dart @@ -2,7 +2,6 @@ import 'package:auto_route/annotations.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:flutter_swipe_action_cell/core/cell.dart'; import 'package:invidious/downloads/views/components/downloaded_video.dart'; import 'package:invidious/globals.dart'; @@ -48,24 +47,10 @@ class DownloadManagerScreen extends StatelessWidget { itemCount: state.videos.length, itemBuilder: (context, index) { var v = state.videos[index]; - return SwipeActionCell( - key: - ValueKey('downloaded-video-${v.videoId}'), - trailingActions: [ - SwipeAction( - performsFirstActionWithFullSwipe: true, - icon: const Icon(Icons.delete, - color: Colors.white), - onTap: (handler) async { - await handler(true); - cubit.deleteVideo(v); - }, - ) - ], - child: DownloadedVideoView( - key: ValueKey(v.videoId), - video: v, - )); + return DownloadedVideoView( + key: ValueKey(v.videoId), + video: v, + ); }, ), ), diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 13694608..ac6f492e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1371,5 +1371,8 @@ "serverAlreadyExists": "Server already exists in settings", "wrongThumbnailConfiguration": "The server is reachable but is not configured properly, the video and channel thumbnails will not be displayed properly. Disable the server test configuration if you are OK with this, fix your server otherwise", "openWikiLink": "Open wiki for help", - "serverUnreachable": "Server is unreachable, or is not a valid invidious server" + "serverUnreachable": "Server is unreachable, or is not a valid invidious server", + "copyToDownloadFolder": "Copy to download folder", + "fileCopiedToDownloadFolder": "File copied to download folder", + "videoDeleted": "Video deleted" } diff --git a/lib/player/states/audio_player.dart b/lib/player/states/audio_player.dart index dc9cbbfa..ee48fdd4 100644 --- a/lib/player/states/audio_player.dart +++ b/lib/player/states/audio_player.dart @@ -139,7 +139,7 @@ class AudioPlayerCubit extends MediaPlayerCubit { } } } else { - String path = await state.offlineVideo!.mediaPath; + String path = await state.offlineVideo!.effectivePath; source = AudioSource.file(path); } diff --git a/lib/player/states/video_player.dart b/lib/player/states/video_player.dart index 4d20b2a2..e36155cb 100644 --- a/lib/player/states/video_player.dart +++ b/lib/player/states/video_player.dart @@ -246,7 +246,7 @@ class VideoPlayerCubit extends MediaPlayerCubit { // getting data sources if (offline) { - String videoPath = await newState.offlineVideo!.mediaPath; + String videoPath = await newState.offlineVideo!.effectivePath; betterPlayerDataSource = BetterPlayerDataSource( BetterPlayerDataSourceType.file, diff --git a/lib/videos/states/download_modal_sheet.dart b/lib/videos/states/download_modal_sheet.dart index ad55eaae..dc57f152 100644 --- a/lib/videos/states/download_modal_sheet.dart +++ b/lib/videos/states/download_modal_sheet.dart @@ -1,17 +1,45 @@ import 'package:bloc/bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:invidious/extensions.dart'; +import 'package:invidious/globals.dart'; +import 'package:invidious/videos/models/adaptive_format.dart'; + +import '../models/base_video.dart'; part 'download_modal_sheet.freezed.dart'; class DownloadModalSheetCubit extends Cubit { - DownloadModalSheetCubit(super.initialState); + final BaseVideo video; + + DownloadModalSheetCubit(super.initialState, this.video) { + init(); + } + + init() async { + emit(state.copyWith(loading: true)); + final vid = await service.getVideo(video.videoId); + + final qualities = vid.adaptiveFormats + .where((f) => f.encoding == 'vp9' && f.type.contains("video/webm")) + .sortBy((f) => int.parse(f.resolution?.replaceAll('p', '') ?? '0')) + .where( + (f) => f.qualityLabel != null && f.qualityLabel!.trim().isNotEmpty) + .toList(); + + emit(state.copyWith( + availableQualities: qualities, + loading: false, + quality: qualities.last.qualityLabel!)); + } setAudioOnly(bool value) { emit(state.copyWith(audioOnly: value)); } - setQuality(String quality) { - emit(state.copyWith(quality: quality)); + setQuality(String? quality) { + if (quality != null) { + emit(state.copyWith(quality: quality)); + } } } @@ -19,5 +47,7 @@ class DownloadModalSheetCubit extends Cubit { class DownloadModalSheetState with _$DownloadModalSheetState { const factory DownloadModalSheetState( {@Default(false) bool audioOnly, + @Default(false) bool loading, + @Default([]) List availableQualities, @Default('720p') String quality}) = _DownloadModalSheetState; } diff --git a/lib/videos/states/download_modal_sheet.freezed.dart b/lib/videos/states/download_modal_sheet.freezed.dart index 8aea32fa..3efca7aa 100644 --- a/lib/videos/states/download_modal_sheet.freezed.dart +++ b/lib/videos/states/download_modal_sheet.freezed.dart @@ -17,6 +17,9 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$DownloadModalSheetState { bool get audioOnly => throw _privateConstructorUsedError; + bool get loading => throw _privateConstructorUsedError; + List get availableQualities => + throw _privateConstructorUsedError; String get quality => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -30,7 +33,11 @@ abstract class $DownloadModalSheetStateCopyWith<$Res> { $Res Function(DownloadModalSheetState) then) = _$DownloadModalSheetStateCopyWithImpl<$Res, DownloadModalSheetState>; @useResult - $Res call({bool audioOnly, String quality}); + $Res call( + {bool audioOnly, + bool loading, + List availableQualities, + String quality}); } /// @nodoc @@ -48,6 +55,8 @@ class _$DownloadModalSheetStateCopyWithImpl<$Res, @override $Res call({ Object? audioOnly = null, + Object? loading = null, + Object? availableQualities = null, Object? quality = null, }) { return _then(_value.copyWith( @@ -55,6 +64,14 @@ class _$DownloadModalSheetStateCopyWithImpl<$Res, ? _value.audioOnly : audioOnly // ignore: cast_nullable_to_non_nullable as bool, + loading: null == loading + ? _value.loading + : loading // ignore: cast_nullable_to_non_nullable + as bool, + availableQualities: null == availableQualities + ? _value.availableQualities + : availableQualities // ignore: cast_nullable_to_non_nullable + as List, quality: null == quality ? _value.quality : quality // ignore: cast_nullable_to_non_nullable @@ -72,7 +89,11 @@ abstract class _$$DownloadModalSheetStateImplCopyWith<$Res> __$$DownloadModalSheetStateImplCopyWithImpl<$Res>; @override @useResult - $Res call({bool audioOnly, String quality}); + $Res call( + {bool audioOnly, + bool loading, + List availableQualities, + String quality}); } /// @nodoc @@ -89,6 +110,8 @@ class __$$DownloadModalSheetStateImplCopyWithImpl<$Res> @override $Res call({ Object? audioOnly = null, + Object? loading = null, + Object? availableQualities = null, Object? quality = null, }) { return _then(_$DownloadModalSheetStateImpl( @@ -96,6 +119,14 @@ class __$$DownloadModalSheetStateImplCopyWithImpl<$Res> ? _value.audioOnly : audioOnly // ignore: cast_nullable_to_non_nullable as bool, + loading: null == loading + ? _value.loading + : loading // ignore: cast_nullable_to_non_nullable + as bool, + availableQualities: null == availableQualities + ? _value._availableQualities + : availableQualities // ignore: cast_nullable_to_non_nullable + as List, quality: null == quality ? _value.quality : quality // ignore: cast_nullable_to_non_nullable @@ -108,18 +139,35 @@ class __$$DownloadModalSheetStateImplCopyWithImpl<$Res> class _$DownloadModalSheetStateImpl implements _DownloadModalSheetState { const _$DownloadModalSheetStateImpl( - {this.audioOnly = false, this.quality = '720p'}); + {this.audioOnly = false, + this.loading = false, + final List availableQualities = const [], + this.quality = '720p'}) + : _availableQualities = availableQualities; @override @JsonKey() final bool audioOnly; + @override + @JsonKey() + final bool loading; + final List _availableQualities; + @override + @JsonKey() + List get availableQualities { + if (_availableQualities is EqualUnmodifiableListView) + return _availableQualities; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_availableQualities); + } + @override @JsonKey() final String quality; @override String toString() { - return 'DownloadModalSheetState(audioOnly: $audioOnly, quality: $quality)'; + return 'DownloadModalSheetState(audioOnly: $audioOnly, loading: $loading, availableQualities: $availableQualities, quality: $quality)'; } @override @@ -129,11 +177,15 @@ class _$DownloadModalSheetStateImpl implements _DownloadModalSheetState { other is _$DownloadModalSheetStateImpl && (identical(other.audioOnly, audioOnly) || other.audioOnly == audioOnly) && + (identical(other.loading, loading) || other.loading == loading) && + const DeepCollectionEquality() + .equals(other._availableQualities, _availableQualities) && (identical(other.quality, quality) || other.quality == quality)); } @override - int get hashCode => Object.hash(runtimeType, audioOnly, quality); + int get hashCode => Object.hash(runtimeType, audioOnly, loading, + const DeepCollectionEquality().hash(_availableQualities), quality); @JsonKey(ignore: true) @override @@ -146,11 +198,17 @@ class _$DownloadModalSheetStateImpl implements _DownloadModalSheetState { abstract class _DownloadModalSheetState implements DownloadModalSheetState { const factory _DownloadModalSheetState( {final bool audioOnly, + final bool loading, + final List availableQualities, final String quality}) = _$DownloadModalSheetStateImpl; @override bool get audioOnly; @override + bool get loading; + @override + List get availableQualities; + @override String get quality; @override @JsonKey(ignore: true) diff --git a/lib/videos/views/components/download_modal_sheet.dart b/lib/videos/views/components/download_modal_sheet.dart index 21a0817a..74d682ea 100644 --- a/lib/videos/views/components/download_modal_sheet.dart +++ b/lib/videos/views/components/download_modal_sheet.dart @@ -1,14 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:gap/gap.dart'; +import 'package:invidious/globals.dart'; import 'package:invidious/main.dart'; +import 'package:invidious/utils/pretty_bytes.dart'; import 'package:invidious/videos/states/download_modal_sheet.dart'; import '../../../downloads/states/download_manager.dart'; import '../../models/base_video.dart'; -const List qualities = ['144p', '360p', '720p']; - class DownloadModalSheet extends StatelessWidget { final BaseVideo video; @@ -66,49 +67,78 @@ class DownloadModalSheet extends StatelessWidget { AppLocalizations locals = AppLocalizations.of(context)!; return BlocProvider( create: (BuildContext context) => - DownloadModalSheetCubit(const DownloadModalSheetState()), + DownloadModalSheetCubit(const DownloadModalSheetState(), video), child: BlocBuilder( builder: (context, state) { var cubit = context.read(); - return Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: ToggleButtons( - isSelected: - qualities.map((e) => e == state.quality).toList(), - onPressed: state.audioOnly - ? null - : (index) => cubit.setQuality(qualities[index]), - children: qualities.map((e) => Text(e)).toList(), - ), - ), - InkWell( - onTap: () => cubit.setAudioOnly(!state.audioOnly), - child: Row( - children: [ - Text(locals.videoDownloadAudioOnly), - Switch( - value: state.audioOnly, - onChanged: cubit.setAudioOnly, - ) - ], + return AnimatedSwitcher( + duration: animationDuration, + switchInCurve: animationCurve, + switchOutCurve: animationCurve, + child: state.loading + ? const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(), ), + ], + ) + : Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Row( + children: [ + Text(locals.quality), + const Gap(10), + DropdownButton( + value: state.availableQualities + .map((e) => e.qualityLabel) + .where((e) => e == state.quality) + .firstOrNull, + onChanged: state.audioOnly + ? null + : (value) => cubit.setQuality(value), + items: state.availableQualities + .map((e) => DropdownMenuItem( + value: e.qualityLabel, + child: Text( + '${e.qualityLabel ?? ''} (~${prettyBytes(double.parse(e.clen))})'))) + .toList(), + ), + ], + ), + ), + InkWell( + onTap: () => cubit.setAudioOnly(!state.audioOnly), + child: Row( + children: [ + Text(locals.videoDownloadAudioOnly), + const Gap(10), + Switch( + value: state.audioOnly, + onChanged: cubit.setAudioOnly, + ) + ], + ), + ), + IconButton.filledTonal( + onPressed: () => downloadVideo(context, state), + icon: const Icon(Icons.download), + ) + ], + ), + ], ), - IconButton.filledTonal( - onPressed: () => downloadVideo(context, state), - icon: const Icon(Icons.download), - ) - ], - ), - ], - ), + ), ); }), ); diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 79bd02b9..8dd8bad0 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -10,7 +10,9 @@ import audio_session import awesome_notifications import awesome_notifications_core import device_info_plus +import downloadsfolder import dynamic_color +import ffmpeg_kit_flutter_full import flutter_web_auth import just_audio import package_info_plus @@ -26,7 +28,9 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AwesomeNotificationsPlugin.register(with: registry.registrar(forPlugin: "AwesomeNotificationsPlugin")) AwesomeNotificationsCorePlugin.register(with: registry.registrar(forPlugin: "AwesomeNotificationsCorePlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + DownloadsfolderPlugin.register(with: registry.registrar(forPlugin: "DownloadsfolderPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) + FFmpegKitFlutterPlugin.register(with: registry.registrar(forPlugin: "FFmpegKitFlutterPlugin")) FlutterWebAuthPlugin.register(with: registry.registrar(forPlugin: "FlutterWebAuthPlugin")) JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) diff --git a/pubspec.lock b/pubspec.lock index e028229a..2f1ef444 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -374,6 +374,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.6" + dartx: + dependency: transitive + description: + name: dartx + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" + url: "https://pub.dev" + source: hosted + version: "1.2.0" dbus: dependency: transitive description: @@ -398,14 +406,38 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + diacritic: + dependency: transitive + description: + name: diacritic + sha256: "96db5db6149cbe4aa3cfcbfd170aca9b7648639be7e48025f9d458517f807fe4" + url: "https://pub.dev" + source: hosted + version: "0.1.5" dio: dependency: "direct main" description: name: dio - sha256: "11e40df547d418cc0c4900a9318b26304e665da6fa4755399a9ff9efd09034b5" + sha256: "0dfb6b6a1979dac1c1245e17cef824d7b452ea29bd33d3467269f9bef3715fb0" + url: "https://pub.dev" + source: hosted + version: "5.6.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" url: "https://pub.dev" source: hosted - version: "5.4.3+1" + version: "2.0.0" + downloadsfolder: + dependency: "direct main" + description: + name: downloadsfolder + sha256: e9987e56b998e3788047f977d31a1b50b4af58c388aefc3d157b9ac5c5d786a2 + url: "https://pub.dev" + source: hosted + version: "1.1.0" dynamic_color: dependency: "direct main" description: @@ -446,6 +478,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + ffmpeg_kit_flutter_full: + dependency: "direct main" + description: + name: ffmpeg_kit_flutter_full + sha256: c845c1bcf40a8869eab9e401526c6cf3fc8091d04c0a7ec9a0af854d6c7e354a + url: "https://pub.dev" + source: hosted + version: "6.0.3" + ffmpeg_kit_flutter_platform_interface: + dependency: transitive + description: + name: ffmpeg_kit_flutter_platform_interface + sha256: addf046ae44e190ad0101b2fde2ad909a3cd08a2a109f6106d2f7048b7abedee + url: "https://pub.dev" + source: hosted + version: "0.2.1" file: dependency: transitive description: @@ -961,10 +1009,10 @@ packages: dependency: "direct main" description: name: path_provider - sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" path_provider_android: dependency: transitive description: @@ -1001,10 +1049,58 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" + permission_handler: + dependency: transitive + description: + name: permission_handler + sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" + url: "https://pub.dev" + source: hosted + version: "11.3.1" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "76e4ab092c1b240d31177bb64d2b0bea43f43d0e23541ec866151b9f7b2490fa" + url: "https://pub.dev" + source: hosted + version: "12.0.12" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0 + url: "https://pub.dev" + source: hosted + version: "9.4.5" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851 + url: "https://pub.dev" + source: hosted + version: "0.1.3+2" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9 + url: "https://pub.dev" + source: hosted + version: "4.2.3" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" petitparser: dependency: transitive description: @@ -1348,6 +1444,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" + time: + dependency: transitive + description: + name: time + sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221 + url: "https://pub.dev" + source: hosted + version: "2.1.4" timing: dependency: transitive description: @@ -1525,13 +1629,13 @@ packages: source: hosted version: "1.1.0" web: - dependency: transitive + dependency: "direct overridden" description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.0.0" web_socket_channel: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 48de2113..567680ac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,10 +18,12 @@ dependencies: copy_with_extension: 5.0.4 cronet_http: 1.3.2 device_info_plus: 10.1.0 - dio: 5.4.3+1 + dio: 5.6.0 + downloadsfolder: 1.1.0 dynamic_color: 1.7.0 easy_debounce: 2.0.3 feedback: 3.1.0 + ffmpeg_kit_flutter_full: 6.0.3 flutter_animate: 4.5.0 flutter_bloc: 8.1.5 flutter_displaymode: 0.6.0 @@ -40,7 +42,7 @@ dependencies: logging: 1.2.0 package_info_plus: 8.0.0 path: any - path_provider: 2.1.3 + path_provider: 2.1.4 receive_sharing_intent: 1.8.0 sembast: 3.7.1 sembast_sqflite: 2.2.0 @@ -87,7 +89,7 @@ dependency_overrides: # cupertino_icons: ^1.0.2 # js: 0.7.1 meta: 1.14.0 - # web: 0.5.0 + web: 1.0.0 flutter: assets: - assets/icon.svg diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index cd568901..827ecca1 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,7 +8,9 @@ #include #include +#include #include +#include #include #include @@ -17,8 +19,12 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("AwesomeNotificationsPluginCApi")); AwesomeNotificationsCorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("AwesomeNotificationsCorePluginCApi")); + DownloadsfolderPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("DownloadsfolderPluginCApi")); DynamicColorPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 08d150d2..2d0310fd 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,7 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST awesome_notifications awesome_notifications_core + downloadsfolder dynamic_color + permission_handler_windows share_plus url_launcher_windows )