diff --git a/AppRun b/AppRun new file mode 100755 index 00000000..533e7601 --- /dev/null +++ b/AppRun @@ -0,0 +1,4 @@ +#!/bin/sh + +cd "$(dirname "$0")" +exec ./askaide diff --git a/Makefile b/Makefile index 850c2d67..a75fbeb1 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,15 @@ build-macos: codesign -f -s "Developer ID Application: YIYAO GUAN (N95437SZ2A)" build/macos/Build/Products/Release/AIdea.app open build/macos/Build/Products/Release/ +build-appimage: + flutter build linux --no-tree-shake-icons --release + mkdir -p aidea_app.AppDir + cp -r build/linux/x64/release/bundle/* aidea_app.AppDir + cp assets/app.png aidea_app.AppDir/ + cp AppRun aidea_app.AppDir/ + cp askaide.desktop aidea_app.AppDir/ + appimagetool aidea_app.AppDir/ + build-web: flutter build web --web-renderer canvaskit --release --dart-define=FLUTTER_WEB_CANVASKIT_URL=https://resources.aicode.cc/canvaskit/ cd scripts && go run main.go ../build/web/main.dart.js && cd .. diff --git a/askaide.desktop b/askaide.desktop new file mode 100644 index 00000000..372a827c --- /dev/null +++ b/askaide.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Version=1.0 +Name=Ask aide +Comment=Ask aide! +Icon=app +Exec=askaide %U +Terminal=false +Type=Application +Categories=Utility; +Keywords=Internet; +StartupNotify=false diff --git a/assets/play.png b/assets/play.png new file mode 100644 index 00000000..994f3508 Binary files /dev/null and b/assets/play.png differ diff --git a/assets/text-to-video.gif b/assets/text-to-video.gif new file mode 100644 index 00000000..04e61785 Binary files /dev/null and b/assets/text-to-video.gif differ diff --git a/docker-build.sh b/docker-build.sh index f8eb7b16..6944fbf9 100755 --- a/docker-build.sh +++ b/docker-build.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -VERSION=1.0.9 +VERSION=1.0.10 rm -fr build/web diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 82851beb..6306382e 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -74,11 +74,21 @@ PODS: - Mantle (2.2.0): - Mantle/extobjc (= 2.2.0) - Mantle/extobjc (2.2.0) + - media_kit_libs_ios_video (1.0.4): + - Flutter + - media_kit_native_event_loop (1.0.0): + - Flutter + - media_kit_video (0.0.1): + - Flutter + - package_info_plus (0.4.5): + - Flutter - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - record (0.0.1): - Flutter + - screen_brightness_ios (0.1.0): + - Flutter - SDWebImage (5.15.5): - SDWebImage/Core (= 5.15.5) - SDWebImage/Core (5.15.5) @@ -100,6 +110,10 @@ PODS: - Flutter - url_launcher_ios (0.0.1): - Flutter + - volume_controller (0.0.1): + - Flutter + - wakelock_plus (0.0.1): + - Flutter - WechatOpenSDK-XCFramework (2.0.2) DEPENDENCIES: @@ -115,14 +129,21 @@ DEPENDENCIES: - fluwx (from `.symlinks/plugins/fluwx/ios`) - image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`) - in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`) + - media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`) + - media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`) + - media_kit_video (from `.symlinks/plugins/media_kit_video/ios`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - record (from `.symlinks/plugins/record/ios`) + - screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) - tobias (from `.symlinks/plugins/tobias/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - volume_controller (from `.symlinks/plugins/volume_controller/ios`) + - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) SPEC REPOS: trunk: @@ -161,10 +182,20 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/image_gallery_saver/ios" in_app_purchase_storekit: :path: ".symlinks/plugins/in_app_purchase_storekit/darwin" + media_kit_libs_ios_video: + :path: ".symlinks/plugins/media_kit_libs_ios_video/ios" + media_kit_native_event_loop: + :path: ".symlinks/plugins/media_kit_native_event_loop/ios" + media_kit_video: + :path: ".symlinks/plugins/media_kit_video/ios" + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" record: :path: ".symlinks/plugins/record/ios" + screen_brightness_ios: + :path: ".symlinks/plugins/screen_brightness_ios/ios" share_plus: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: @@ -177,6 +208,10 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/tobias/ios" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" + volume_controller: + :path: ".symlinks/plugins/volume_controller/ios" + wakelock_plus: + :path: ".symlinks/plugins/wakelock_plus/ios" SPEC CHECKSUMS: audioplayers_darwin: 877d9a4d06331c5c374595e46e16453ac7eafa40 @@ -196,19 +231,26 @@ SPEC CHECKSUMS: in_app_purchase_storekit: 4fb7ee9e824b1f09107fbfbbce8c4b276366dc43 libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d + media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1 + media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a + media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e + package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 record: cae05d8dd3cdb1dea3511b20e5a5811a1ae00d0d + screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 SDWebImage: fd7e1a22f00303e058058278639bf6196ee431fe SDWebImageWebPCoder: 295a6573c512f54ad2dd58098e64e17dcf008499 - share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 + share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 sign_in_with_apple: f3bf75217ea4c2c8b91823f225d70230119b8440 sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f tobias: 2aded9b83e3663b907360a800d8e3c13284f25c5 url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 + volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9 + wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47 WechatOpenSDK-XCFramework: acdeeda129efbef9532bca8a10c24e1b4b8c7d69 PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048 -COCOAPODS: 1.12.1 +COCOAPODS: 1.14.3 diff --git a/lib/bloc/creative_island_bloc.dart b/lib/bloc/creative_island_bloc.dart index 575766ea..cb094faa 100644 --- a/lib/bloc/creative_island_bloc.dart +++ b/lib/bloc/creative_island_bloc.dart @@ -104,6 +104,7 @@ class CreativeIslandBloc }); on((event, emit) async { + emit(CreativeIslandHistoryItemLoading()); emit(CreativeIslandHistoryItemLoaded( item: await APIServer().creativeHistoryItem( hisId: event.itemId, diff --git a/lib/bloc/creative_island_state.dart b/lib/bloc/creative_island_state.dart index 51846090..5606fcda 100644 --- a/lib/bloc/creative_island_state.dart +++ b/lib/bloc/creative_island_state.dart @@ -67,6 +67,8 @@ class CreativeIslandListLoaded extends CreativeIslandState { get error => _error; } +class CreativeIslandHistoryItemLoading extends CreativeIslandState {} + class CreativeIslandHistoryItemLoaded extends CreativeIslandState { final Object? error; final CreativeItemInServer? item; diff --git a/lib/helper/constant.dart b/lib/helper/constant.dart index 8505ccec..9372331a 100644 --- a/lib/helper/constant.dart +++ b/lib/helper/constant.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; // 客户端应用版本号 -const clientVersion = '1.0.10'; +const clientVersion = '1.0.11'; // 本地数据库版本号 const databaseVersion = 26; diff --git a/lib/helper/env.dart b/lib/helper/env.dart index 0346c798..67b6c17c 100644 --- a/lib/helper/env.dart +++ b/lib/helper/env.dart @@ -1,3 +1,5 @@ +import 'dart:io' show Platform; + /// 默认 API 服务器地址 /// 注意:当你使用自己的服务器时,请修改该地址为你自己的服务器地址 const defaultAPIServerURL = 'https://ai-api.aicode.cc'; @@ -16,3 +18,16 @@ String get apiServerURL { return url; } + +String get getDbBasePath { + String home=''; + Map envVars = Platform.environment; + if (Platform.isMacOS) { + home = envVars['HOME']!; + } else if (Platform.isLinux) { + home = envVars['HOME']!; + } else if (Platform.isWindows) { + home = envVars['UserProfile']!; + } + return home; +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 5e7b2012..6fd7add1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,5 @@ +import 'package:path/path.dart'; + import 'package:askaide/bloc/account_bloc.dart'; import 'package:askaide/bloc/background_image_bloc.dart'; import 'package:askaide/bloc/chat_chat_bloc.dart'; @@ -100,9 +102,13 @@ import 'page/component/theme/theme.dart'; import 'package:sizer/sizer.dart'; import 'package:askaide/helper/http.dart' as httpx; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; +import 'package:media_kit/media_kit.dart'; + +import 'package:askaide/helper/env.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); + MediaKit.ensureInitialized(); httpx.HttpClient.init(); // FlutterError.onError = (FlutterErrorDetails details) { @@ -121,6 +127,8 @@ void main() async { if (PlatformTool.isWindows() || PlatformTool.isLinux()) { sqfliteFfiInit(); databaseFactory = databaseFactoryFfi; + var path=absolute(join(getDbBasePath, '.aideal', 'databases')); + databaseFactory.setDatabasesPath(path); } } @@ -226,7 +234,6 @@ class MyApp extends StatefulWidget { // Bloc late final RoomBloc chatRoomBloc; - late final CreativeIslandBloc creativeIslandBloc; late final GalleryBloc galleryBloc; late final AccountBloc accountBloc; late final VersionBloc versionBloc; @@ -248,7 +255,6 @@ class MyApp extends StatefulWidget { }) { chatRoomBloc = RoomBloc(chatMsgRepo: chatMsgRepo, stateManager: messageStateManager); - creativeIslandBloc = CreativeIslandBloc(creativeIslandRepo); accountBloc = AccountBloc(settingRepo); versionBloc = VersionBloc(); galleryBloc = GalleryBloc(); @@ -527,7 +533,9 @@ class MyApp extends StatefulWidget { MultiBlocProvider( providers: [ BlocProvider.value(value: accountBloc), - BlocProvider.value(value: creativeIslandBloc), + BlocProvider( + create: (context) => + CreativeIslandBloc(creativeIslandRepo)), ], child: UserCenterScreen( settings: context.read()), @@ -577,7 +585,9 @@ class MyApp extends StatefulWidget { pageBuilder: (context, state) => transitionResolver( MultiBlocProvider( providers: [ - BlocProvider.value(value: creativeIslandBloc), + BlocProvider( + create: (context) => + CreativeIslandBloc(creativeIslandRepo)), ], child: DrawListScreen( setting: settingRepo, @@ -592,13 +602,17 @@ class MyApp extends StatefulWidget { pageBuilder: (context, state) => transitionResolver( MultiBlocProvider( providers: [ - BlocProvider.value(value: creativeIslandBloc), + BlocProvider( + create: (context) => + CreativeIslandBloc(creativeIslandRepo)), ], child: ImageEditDirectScreen( setting: settingRepo, title: AppLocale.superResolution.getString(context), apiEndpoint: 'upscale', note: state.queryParameters['note'], + initWaitDuration: 15, + initImage: state.queryParameters['init_image'], ), ), ), @@ -610,13 +624,39 @@ class MyApp extends StatefulWidget { pageBuilder: (context, state) => transitionResolver( MultiBlocProvider( providers: [ - BlocProvider.value(value: creativeIslandBloc), + BlocProvider( + create: (context) => + CreativeIslandBloc(creativeIslandRepo)), ], child: ImageEditDirectScreen( setting: settingRepo, title: AppLocale.colorizeImage.getString(context), apiEndpoint: 'colorize', note: state.queryParameters['note'], + initWaitDuration: 15, + initImage: state.queryParameters['init_image'], + ), + ), + ), + ), + GoRoute( + name: 'creative-video', + path: '/creative-draw/create-video', + parentNavigatorKey: _shellNavigatorKey, + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => + CreativeIslandBloc(creativeIslandRepo)), + ], + child: ImageEditDirectScreen( + setting: settingRepo, + title: '图生视频', + apiEndpoint: 'image-to-video', + note: state.queryParameters['note'], + initWaitDuration: 60, + initImage: state.queryParameters['init_image'], ), ), ), @@ -653,6 +693,8 @@ class MyApp extends StatefulWidget { ), mode: state.queryParameters['mode']!, id: state.queryParameters['id']!, + note: state.queryParameters['note'], + initImage: state.queryParameters['init_image'], ), ), ), @@ -673,6 +715,7 @@ class MyApp extends StatefulWidget { ), type: state.queryParameters['type']!, id: state.queryParameters['id']!, + note: state.queryParameters['note'], ), ), ), @@ -685,7 +728,9 @@ class MyApp extends StatefulWidget { return transitionResolver( MultiBlocProvider( providers: [ - BlocProvider.value(value: creativeIslandBloc), + BlocProvider( + create: (context) => + CreativeIslandBloc(creativeIslandRepo)), ], child: MyCreationScreen( setting: settingRepo, @@ -703,7 +748,9 @@ class MyApp extends StatefulWidget { return transitionResolver( MultiBlocProvider( providers: [ - BlocProvider.value(value: creativeIslandBloc), + BlocProvider( + create: (context) => + CreativeIslandBloc(creativeIslandRepo)), ], child: CreativeModelScreen(setting: settingRepo), ), @@ -722,7 +769,9 @@ class MyApp extends StatefulWidget { return transitionResolver( MultiBlocProvider( providers: [ - BlocProvider.value(value: creativeIslandBloc), + BlocProvider( + create: (context) => + CreativeIslandBloc(creativeIslandRepo)), ], child: MyCreationItemPage( setting: settingRepo, diff --git a/lib/page/component/button.dart b/lib/page/component/button.dart index 987cfc3b..71500ba4 100644 --- a/lib/page/component/button.dart +++ b/lib/page/component/button.dart @@ -8,6 +8,7 @@ class Button extends StatelessWidget { final Color? backgroundColor; final Color? color; + final Widget? icon; const Button({ super.key, @@ -16,6 +17,7 @@ class Button extends StatelessWidget { this.size = const ButtonSize.small(), this.backgroundColor, this.color, + this.icon, }); @override @@ -28,6 +30,7 @@ class Button extends StatelessWidget { onPressed: onPressed, backgroundColor: backgroundColor, color: color, + icon: icon, ); } } diff --git a/lib/page/component/chat/chat_preview.dart b/lib/page/component/chat/chat_preview.dart index bbf76cce..89d50327 100644 --- a/lib/page/component/chat/chat_preview.dart +++ b/lib/page/component/chat/chat_preview.dart @@ -328,7 +328,7 @@ class _ChatPreviewState extends State { ? Markdown( data: text.trim(), onUrlTap: (value) => - launchUrlString(value), + onMarkdownUrlTap(value), ) : SelectableText( text, diff --git a/lib/page/component/dialog.dart b/lib/page/component/dialog.dart index 07307346..62e77165 100644 --- a/lib/page/component/dialog.dart +++ b/lib/page/component/dialog.dart @@ -118,8 +118,10 @@ showCustomBeautyDialog( showBeautyDialog( BuildContext context, { required QuickAlertType type, - required String text, + String? text, String? title, + String? customAsset, + Widget? widget, String confirmBtnText = '确定', String? cancelBtnText, Function()? onConfirmBtnTap, @@ -133,6 +135,8 @@ showBeautyDialog( context: context, type: type, text: text, + customAsset: customAsset, + widget: widget, width: MediaQuery.of(context).size.width > 600 ? 400 : null, barrierDismissible: barrierDismissible, showCancelBtn: showCancelBtn, diff --git a/lib/page/component/image.dart b/lib/page/component/image.dart index 27d4e46b..b2b82fd3 100644 --- a/lib/page/component/image.dart +++ b/lib/page/component/image.dart @@ -1,11 +1,19 @@ import 'dart:io'; +import 'package:askaide/helper/image.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; -ImageProvider resolveImageProvider(String imageUrl) { +ImageProvider resolveImageProvider( + String imageUrl, { + bool useThumbnail = true, +}) { if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) { + if (useThumbnail) { + imageUrl = imageURL(imageUrl, 'thumb'); + } + return CachedNetworkImageProviderEnhanced(imageUrl); } diff --git a/lib/page/component/image_action.dart b/lib/page/component/image_action.dart new file mode 100644 index 00000000..b54e0530 --- /dev/null +++ b/lib/page/component/image_action.dart @@ -0,0 +1,184 @@ +import 'package:askaide/lang/lang.dart'; +import 'package:askaide/page/component/button.dart'; +import 'package:askaide/page/component/dialog.dart'; +import 'package:askaide/page/component/loading.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; +import 'package:askaide/repo/api/creative.dart'; +import 'package:askaide/repo/api_server.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_localization/flutter_localization.dart'; +import 'package:go_router/go_router.dart'; + +Future openImageWorkflowActionDialog( + BuildContext context, + CustomColors customColors, + String imageUrl, +) { + return openModalBottomSheet( + context, + (context) { + return Column( + children: [ + Text('选择要执行的操作', + style: TextStyle( + color: customColors.weakTextColorPlus, + )), + Expanded( + child: FutureBuilder( + future: APIServer().creativeIslandItemsV2(), + builder: (context, snapshot) { + if (snapshot.hasData) { + Map itemsMap = {}; + for (var item in snapshot.data!) { + itemsMap[item.id] = item; + } + + return actionsBuilder( + itemsMap, customColors, context, imageUrl); + } + + return const LoadingIndicator( + message: '加载中,请稍候...', + ); + }, + ), + ), + ], + ); + }, + heightFactor: 0.5, + ); +} + +Widget actionsBuilder( + Map itemsMap, + CustomColors customColors, + BuildContext context, + String imageUrl, +) { + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(height: 20), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (itemsMap.containsKey('image-to-image')) + Button( + title: '图生图', + icon: Icon( + Icons.collections_outlined, + size: 16, + color: customColors.weakLinkColor, + ), + onPressed: () { + context.pop(); + + context.push(Uri( + path: '/creative-draw/create', + queryParameters: { + 'id': 'image-to-image', + 'mode': 'image-to-image', + 'note': itemsMap['image-to-image']!.note, + 'init_image': imageUrl, + }, + ).toString()); + }, + size: const ButtonSize.full(), + color: customColors.weakLinkColor, + backgroundColor: const Color.fromARGB(34, 183, 183, 183), + ), + if (itemsMap.containsKey('image-to-image')) + const SizedBox(height: 10), + if (itemsMap.containsKey('image-to-video')) + Button( + title: '图生视频', + icon: Icon( + Icons.video_camera_back_outlined, + size: 16, + color: customColors.weakLinkColor, + ), + onPressed: () { + context.pop(); + + context.push(Uri( + path: '/creative-draw/create-video', + queryParameters: { + 'note': itemsMap['image-to-video']!.note, + 'init_image': imageUrl, + }, + ).toString()); + }, + size: const ButtonSize.full(), + color: customColors.weakLinkColor, + backgroundColor: const Color.fromARGB(34, 183, 183, 183), + ), + if (itemsMap.containsKey('image-to-video')) + const SizedBox(height: 10), + if (itemsMap.containsKey('image-upscale')) + Button( + title: '高清修复', + icon: Icon( + Icons.hd_outlined, + size: 16, + color: customColors.weakLinkColor, + ), + onPressed: () { + context.pop(); + + context.push(Uri( + path: '/creative-draw/create-upscale', + queryParameters: { + 'note': itemsMap['image-upscale']!.note, + 'init_image': imageUrl, + }, + ).toString()); + }, + size: const ButtonSize.full(), + color: customColors.weakLinkColor, + backgroundColor: const Color.fromARGB(34, 183, 183, 183), + ), + if (itemsMap.containsKey('image-upscale')) + const SizedBox(height: 10), + if (itemsMap.containsKey('image-colorize')) + Button( + title: '图片上色', + icon: Icon( + Icons.palette_outlined, + size: 16, + color: customColors.weakLinkColor, + ), + onPressed: () { + context.pop(); + context.push(Uri( + path: '/creative-draw/create-colorize', + queryParameters: { + 'note': itemsMap['image-colorize']!.note, + 'init_image': imageUrl, + }, + ).toString()); + }, + size: const ButtonSize.full(), + color: customColors.weakLinkColor, + backgroundColor: const Color.fromARGB(34, 183, 183, 183), + ), + ], + ), + ), + Container( + margin: const EdgeInsets.only(bottom: 20), + child: Button( + title: AppLocale.cancel.getString(context), + backgroundColor: const Color.fromARGB(36, 222, 222, 222), + color: customColors.dialogDefaultTextColor?.withAlpha(150), + onPressed: () { + context.pop(); + }, + size: const ButtonSize.full(), + ), + ), + ], + ); +} diff --git a/lib/page/component/image_preview.dart b/lib/page/component/image_preview.dart index 89872adb..3147ca5e 100644 --- a/lib/page/component/image_preview.dart +++ b/lib/page/component/image_preview.dart @@ -6,6 +6,7 @@ import 'package:askaide/helper/platform.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/gallery_item_share.dart'; import 'package:askaide/page/component/image.dart'; +import 'package:askaide/page/component/image_action.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; @@ -135,6 +136,36 @@ class _NetworkImagePreviewerState extends State { ); }, ), + IconButton( + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + hoverColor: Colors.transparent, + icon: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.webhook, + size: 14, + color: customColors.weakLinkColor, + ), + const SizedBox(width: 5), + Text( + '动作', + style: TextStyle( + fontSize: 12, + color: customColors.weakLinkColor, + ), + ), + ], + ), + onPressed: () { + openImageWorkflowActionDialog( + context, + customColors, + widget.url, + ); + }, + ), IconButton( splashColor: Colors.transparent, highlightColor: Colors.transparent, diff --git a/lib/page/component/video_player.dart b/lib/page/component/video_player.dart new file mode 100644 index 00000000..a4821655 --- /dev/null +++ b/lib/page/component/video_player.dart @@ -0,0 +1,160 @@ +import 'package:askaide/helper/helper.dart'; +import 'package:askaide/helper/logger.dart'; +import 'package:askaide/helper/platform.dart'; +import 'package:askaide/page/component/dialog.dart'; +import 'package:askaide/page/component/loading.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; +import 'package:bot_toast/bot_toast.dart'; +import 'package:file_saver/file_saver.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:media_kit/media_kit.dart'; +import 'package:media_kit_video/media_kit_video.dart'; + +class VideoPlayer extends StatefulWidget { + final String url; + final double? aspectRatio; + final int? width; + final int? height; + + const VideoPlayer( + {super.key, + required this.url, + this.width, + this.height, + this.aspectRatio}); + + @override + State createState() => _VideoPlayerState(); +} + +class _VideoPlayerState extends State { + late final player = Player(); + late final controller = VideoController(player); + + @override + void initState() { + super.initState(); + player.setPlaylistMode(PlaylistMode.single); + player.open(Media(widget.url)); + } + + @override + void dispose() { + player.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final customColors = Theme.of(context).extension()!; + + return Container( + decoration: BoxDecoration( + color: customColors.columnBlockBackgroundColor, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(8), + topRight: Radius.circular(8), + ), + child: Center( + child: SizedBox( + width: MediaQuery.of(context).size.width, + height: widget.width != null && widget.height != null + ? MediaQuery.of(context).size.width * + widget.height! / + widget.width! + : MediaQuery.of(context).size.width, + child: Video( + controller: controller, + width: widget.width?.toDouble(), + height: widget.height?.toDouble(), + aspectRatio: widget.aspectRatio, + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + hoverColor: Colors.transparent, + icon: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.download, + size: 14, + color: customColors.weakLinkColor, + ), + const SizedBox(width: 5), + Text( + '下载', + style: TextStyle( + fontSize: 12, + color: customColors.weakLinkColor, + ), + ), + ], + ), + onPressed: () async { + final cancel = BotToast.showCustomLoading( + toastBuilder: (cancel) { + return const LoadingIndicator( + message: '下载中,请稍候...', + ); + }, + allowClick: false, + duration: const Duration(seconds: 120), + ); + + try { + final saveFile = + await DefaultCacheManager().getSingleFile(widget.url); + + if (PlatformTool.isIOS() || PlatformTool.isAndroid()) { + await ImageGallerySaver.saveFile(saveFile.path); + + showSuccessMessage('保存成功'); + } else { + var ext = saveFile.path.toLowerCase().split('.').last; + + FileSaver.instance + .saveFile( + name: + filenameWithoutExt(saveFile.path.split('/').last), + filePath: saveFile.path, + ext: ext, + mimeType: MimeType.mpeg, + ) + .then((value) { + showSuccessMessage('文件保存成功'); + }); + } + } catch (e) { + // ignore: use_build_context_synchronously + showErrorMessageEnhanced(context, '保存失败,请稍后再试'); + Logger.instance.e('下载失败', error: e); + } finally { + cancel(); + } + }, + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/page/creative_island/draw/artistic_text.dart b/lib/page/creative_island/draw/artistic_text.dart index 5faa2e86..5120ac14 100644 --- a/lib/page/creative_island/draw/artistic_text.dart +++ b/lib/page/creative_island/draw/artistic_text.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:askaide/helper/ability.dart'; +import 'package:askaide/helper/cache.dart'; import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; @@ -35,12 +36,15 @@ class ArtisticTextScreen extends StatefulWidget { final int? galleryCopyId; final String type; final String id; + final String? note; + const ArtisticTextScreen({ super.key, required this.id, required this.setting, this.galleryCopyId, required this.type, + this.note, }); @override @@ -123,6 +127,24 @@ class _ArtisticTextScreenState extends State { } }); + if (widget.note != null) { + Cache() + .boolGet(key: 'creative:tutorials:${widget.type}:dialog') + .then((show) { + if (!show) { + return; + } + + openDefaultTutorials(onConfirm: () { + Cache().setBool( + key: 'creative:tutorials:${widget.type}:dialog', + value: false, + duration: const Duration(days: 30), + ); + }); + }); + } + super.initState(); } @@ -144,6 +166,15 @@ class _ArtisticTextScreenState extends State { ), toolbarHeight: CustomSize.toolbarHeight, backgroundColor: customColors.backgroundContainerColor, + actions: [ + if (widget.note != null) + IconButton( + onPressed: () { + openDefaultTutorials(); + }, + icon: const Icon(Icons.help_outline), + ) + ], ), backgroundColor: customColors.backgroundContainerColor, body: BackgroundContainer( @@ -170,6 +201,20 @@ class _ArtisticTextScreenState extends State { ); } + void openDefaultTutorials({Function? onConfirm}) { + showBeautyDialog( + context, + type: QuickAlertType.info, + text: ' ${widget.note!}', + onConfirmBtnTap: () async { + onConfirm?.call(); + context.pop(); + }, + showCancelBtn: true, + confirmBtnText: AppLocale.gotIt.getString(context), + ); + } + Widget buildEditPanel(BuildContext context, CustomColors customColors) { return SafeArea( child: Column( diff --git a/lib/page/creative_island/draw/components/content_preview.dart b/lib/page/creative_island/draw/components/content_preview.dart index e70e9178..fd75ccca 100644 --- a/lib/page/creative_island/draw/components/content_preview.dart +++ b/lib/page/creative_island/draw/components/content_preview.dart @@ -2,6 +2,7 @@ import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/image.dart'; import 'package:askaide/page/component/image_preview.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; +import 'package:askaide/page/component/video_player.dart'; import 'package:askaide/repo/api/creative.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -80,10 +81,17 @@ class _CreativeIslandContentPreviewState vertical: 5, horizontal: 10, ), - child: _buildImagePreviewer( - widget.result.params ?? {}, - e, - ), + child: (widget.item != null && + (widget.item!.isVideoType)) || + e.endsWith('.mp4') + ? _buildVideoPreviewer( + widget.result.params ?? {}, + e, + ) + : _buildImagePreviewer( + widget.result.params ?? {}, + e, + ), ), ) .toList()), @@ -92,6 +100,20 @@ class _CreativeIslandContentPreviewState ); } + Widget _buildVideoPreviewer( + Map params, + String e, + ) { + int? width = params['width'] == null ? null : params['width'] as int; + int? height = params['height'] == null ? null : params['height'] as int; + + return VideoPlayer( + url: e, + width: width, + height: height, + ); + } + Widget _buildImagePreviewer( Map params, String e, diff --git a/lib/page/creative_island/draw/components/image_selector.dart b/lib/page/creative_island/draw/components/image_selector.dart index aae89e75..0ad61f34 100644 --- a/lib/page/creative_island/draw/components/image_selector.dart +++ b/lib/page/creative_island/draw/components/image_selector.dart @@ -49,20 +49,12 @@ class ImageSelector extends StatelessWidget { if (titleHelper != null) titleHelper!, ], ), - // IconButton( - // onPressed: () { - // HapticFeedbackHelper.mediumImpact(); - // onImageSelected(null); - // }, - // icon: const Icon(Icons.refresh), - // iconSize: 15, - // tooltip: '重置', - // ) ], ), if (title != null) const SizedBox(height: 10), Material( borderRadius: BorderRadius.circular(8), + color: customColors.backgroundColor, child: InkWell( borderRadius: BorderRadius.circular(8), onTap: () async { @@ -140,9 +132,9 @@ class ImageSelector extends StatelessWidget { child: Container( color: const Color.fromARGB(80, 255, 255, 255), height: 50, - child: Row( + child: const Row( mainAxisAlignment: MainAxisAlignment.center, - children: const [ + children: [ Icon( Icons.camera_alt, size: 30, diff --git a/lib/page/creative_island/draw/draw_create.dart b/lib/page/creative_island/draw/draw_create.dart index 97194e8e..fe0f1845 100644 --- a/lib/page/creative_island/draw/draw_create.dart +++ b/lib/page/creative_island/draw/draw_create.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:askaide/helper/ability.dart'; +import 'package:askaide/helper/cache.dart'; import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/helper/upload.dart'; @@ -13,6 +14,7 @@ import 'package:askaide/page/component/enhanced_textfield.dart'; import 'package:askaide/page/component/global_alert.dart'; import 'package:askaide/page/component/item_selector_search.dart'; import 'package:askaide/page/component/loading.dart'; +import 'package:askaide/page/component/message_box.dart'; import 'package:askaide/page/component/prompt_tags_selector.dart'; import 'package:askaide/page/creative_island/draw/components/content_preview.dart'; import 'package:askaide/page/creative_island/draw/draw_result.dart'; @@ -39,12 +41,17 @@ class DrawCreateScreen extends StatefulWidget { final int? galleryCopyId; final String mode; final String id; + final String? note; + final String? initImage; + const DrawCreateScreen({ super.key, required this.id, required this.setting, this.galleryCopyId, required this.mode, + this.note, + this.initImage, }); @override @@ -85,6 +92,10 @@ class _DrawCreateScreenState extends State { @override void initState() { + if (widget.initImage != null) { + selectedImagePath = widget.initImage; + } + APIServer() .creativeIslandCapacity(mode: widget.mode, id: widget.id) .then((cap) { @@ -165,9 +176,41 @@ class _DrawCreateScreenState extends State { } }); + if (widget.note != null) { + Cache() + .boolGet(key: 'creative:tutorials:${widget.mode}:dialog') + .then((show) { + if (!show) { + return; + } + + openDefaultTutorials(onConfirm: () { + Cache().setBool( + key: 'creative:tutorials:${widget.mode}:dialog', + value: false, + duration: const Duration(days: 30), + ); + }); + }); + } + super.initState(); } + void openDefaultTutorials({Function? onConfirm}) { + showBeautyDialog( + context, + type: QuickAlertType.info, + text: ' ${widget.note!}', + onConfirmBtnTap: () async { + onConfirm?.call(); + context.pop(); + }, + showCancelBtn: true, + confirmBtnText: AppLocale.gotIt.getString(context), + ); + } + @override Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; @@ -188,6 +231,15 @@ class _DrawCreateScreenState extends State { ), toolbarHeight: CustomSize.toolbarHeight, backgroundColor: customColors.backgroundContainerColor, + actions: [ + if (widget.note != null) + IconButton( + onPressed: () { + openDefaultTutorials(); + }, + icon: const Icon(Icons.help_outline), + ) + ], ), backgroundColor: customColors.backgroundContainerColor, body: BackgroundContainer( @@ -230,12 +282,14 @@ class _DrawCreateScreenState extends State { if (path != null) { setState(() { selectedImagePath = path; + selectedImageData = null; }); } if (data != null) { setState(() { selectedImageData = data; + selectedImagePath = null; }); } }, @@ -801,7 +855,7 @@ class _DrawCreateScreenState extends State { 'prompt': prompt, 'negative_prompt': negativePromptController.text, 'prompt_tags': selectedTags.map((e) => e.value).join(','), - 'filter_id': selectedStyle == null ? null : selectedStyle!.id, + 'filter_id': selectedStyle?.id, 'image_ratio': selectedImageSize, 'image_count': generationImageCount, 'ai_rewrite': enableAIRewrite, @@ -845,17 +899,24 @@ class _DrawCreateScreenState extends State { allowClick: false, ); - if (selectedImagePath != null && selectedImagePath!.isNotEmpty) { - final uploadRes = await ImageUploader(widget.setting) - .upload(selectedImagePath!) - .whenComplete(() => cancel()); - params['image'] = uploadRes.url; - } else if (selectedImageData != null && - selectedImageData!.isNotEmpty) { - final uploadRes = await ImageUploader(widget.setting) - .uploadData(selectedImageData!) - .whenComplete(() => cancel()); - params['image'] = uploadRes.url; + if (selectedImagePath != null && + (selectedImagePath!.startsWith('http://') || + selectedImagePath!.startsWith('https://'))) { + params['image'] = selectedImagePath; + cancel(); + } else { + if (selectedImagePath != null && selectedImagePath!.isNotEmpty) { + final uploadRes = await ImageUploader(widget.setting) + .upload(selectedImagePath!) + .whenComplete(() => cancel()); + params['image'] = uploadRes.url; + } else if (selectedImageData != null && + selectedImageData!.isNotEmpty) { + final uploadRes = await ImageUploader(widget.setting) + .uploadData(selectedImageData!) + .whenComplete(() => cancel()); + params['image'] = uploadRes.url; + } } } diff --git a/lib/page/creative_island/draw/image_edit_direct.dart b/lib/page/creative_island/draw/image_edit_direct.dart index 171b8508..1f4db109 100644 --- a/lib/page/creative_island/draw/image_edit_direct.dart +++ b/lib/page/creative_island/draw/image_edit_direct.dart @@ -1,10 +1,12 @@ import 'package:askaide/helper/ability.dart'; +import 'package:askaide/helper/cache.dart'; import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/helper/upload.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/column_block.dart'; import 'package:askaide/page/component/enhanced_button.dart'; +import 'package:askaide/page/component/enhanced_textfield.dart'; import 'package:askaide/page/component/global_alert.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/message_box.dart'; @@ -17,22 +19,28 @@ import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:go_router/go_router.dart'; +import 'package:quickalert/quickalert.dart'; class ImageEditDirectScreen extends StatefulWidget { final SettingRepository setting; final String title; final String apiEndpoint; final String? note; + final int initWaitDuration; + final String? initImage; + const ImageEditDirectScreen({ super.key, required this.setting, required this.title, required this.apiEndpoint, this.note, + this.initWaitDuration = 30, + this.initImage, }); @override @@ -43,9 +51,95 @@ class _ImageEditDirectScreenState extends State { String? selectedImagePath; Uint8List? selectedImageData; + TextEditingController seedController = TextEditingController(); + double? cfgScale = 0.0; + int? motionBucketId = 0; + + bool showAdvancedOptions = false; + /// 是否停止周期性查询任务执行状态 var stopPeriodQuery = false; + @override + void initState() { + if (widget.initImage != null && widget.initImage!.isNotEmpty) { + selectedImagePath = widget.initImage; + } + + if (widget.note != null) { + if (widget.apiEndpoint == 'image-to-video') { + Cache() + .boolGet(key: 'creative:tutorials:${widget.apiEndpoint}:dialog') + .then((show) { + if (!show) { + return; + } + + openImageToVideoTutorials(onConfirm: () { + Cache().setBool( + key: 'creative:tutorials:${widget.apiEndpoint}:dialog', + value: false, + duration: const Duration(days: 30), + ); + }); + }); + } else { + Cache() + .boolGet(key: 'creative:tutorials:${widget.apiEndpoint}:dialog') + .then((show) { + if (!show) { + return; + } + + openDefaultTutorials(onConfirm: () { + Cache().setBool( + key: 'creative:tutorials:${widget.apiEndpoint}:dialog', + value: false, + duration: const Duration(days: 30), + ); + }); + }); + } + } + + super.initState(); + } + + void openDefaultTutorials({Function? onConfirm}) { + showBeautyDialog( + context, + type: QuickAlertType.info, + text: ' ${widget.note!}', + onConfirmBtnTap: () async { + onConfirm?.call(); + context.pop(); + }, + showCancelBtn: true, + confirmBtnText: AppLocale.gotIt.getString(context), + ); + } + + void openImageToVideoTutorials({Function? onConfirm}) { + showBeautyDialog( + context, + type: QuickAlertType.custom, + widget: Text(' ${widget.note!}'), + customAsset: 'assets/text-to-video.gif', + onConfirmBtnTap: () async { + onConfirm?.call(); + context.pop(); + }, + showCancelBtn: true, + confirmBtnText: AppLocale.gotIt.getString(context), + ); + } + + @override + void dispose() { + seedController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; @@ -64,6 +158,22 @@ class _ImageEditDirectScreenState extends State { ), toolbarHeight: CustomSize.toolbarHeight, backgroundColor: customColors.backgroundContainerColor, + actions: [ + if (widget.note != null && widget.apiEndpoint == 'image-to-video') + IconButton( + onPressed: () { + openImageToVideoTutorials(); + }, + icon: const Icon(Icons.help_outline), + ) + else if (widget.note != null) + IconButton( + onPressed: () { + openDefaultTutorials(); + }, + icon: const Icon(Icons.help_outline), + ), + ], ), backgroundColor: customColors.backgroundContainerColor, body: BackgroundContainer( @@ -95,12 +205,6 @@ class _ImageEditDirectScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (widget.note != null && widget.note != '') - Padding( - padding: const EdgeInsets.only(bottom: 10), - child: - MessageBox(message: widget.note!, type: MessageBoxType.info), - ), ColumnBlock( innerPanding: 10, padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), @@ -111,12 +215,14 @@ class _ImageEditDirectScreenState extends State { if (path != null) { setState(() { selectedImagePath = path; + selectedImageData = null; }); } if (data != null) { setState(() { selectedImageData = data; + selectedImagePath = null; }); } }, @@ -127,10 +233,172 @@ class _ImageEditDirectScreenState extends State { ), ], ), + if (showAdvancedOptions) + ColumnBlock( + innerPanding: 10, + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), + children: [ + // Cfg Scale + Column( + children: [ + Row( + children: [ + const Text('Cfg Scale'), + const SizedBox(width: 5), + InkWell( + onTap: () { + showBeautyDialog( + context, + type: QuickAlertType.info, + text: + 'How strongly the video sticks to the original image. \nUse lower values to allow the model more freedom to make changes and higher values to correct motion distortions', + confirmBtnText: + AppLocale.gotIt.getString(context), + showCancelBtn: false, + ); + }, + child: Icon( + Icons.help_outline, + size: 16, + color: customColors.weakLinkColor?.withAlpha(150), + ), + ), + ], + ), + Row( + children: [ + Expanded( + child: Slider( + value: cfgScale ?? 0.0, + min: 0, + max: 10, + divisions: 20, + label: cfgScaleText(cfgScale), + activeColor: customColors.linkColor, + onChanged: (value) { + setState(() { + if (value > 0 && value < 1) { + value = 1; + } + + cfgScale = value; + }); + }, + ), + ), + Text( + cfgScaleText(cfgScale), + style: TextStyle( + fontSize: 12, + color: customColors.weakTextColor, + ), + ), + ], + ) + ], + ), + // Motion Bucket ID + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + const Text('Motion Bucket ID'), + const SizedBox(width: 5), + InkWell( + onTap: () { + showBeautyDialog( + context, + type: QuickAlertType.info, + text: + 'Lower values generally result in less motion in the output video, \nwhile higher values generally result in more motion', + confirmBtnText: + AppLocale.gotIt.getString(context), + showCancelBtn: false, + ); + }, + child: Icon( + Icons.help_outline, + size: 16, + color: customColors.weakLinkColor?.withAlpha(150), + ), + ), + ], + ), + Row( + children: [ + Expanded( + child: Slider( + value: (motionBucketId ?? 0).toDouble(), + min: 0, + max: 255, + divisions: 51, + label: motionBucketIdText(motionBucketId), + activeColor: customColors.linkColor, + onChanged: (value) { + setState(() { + if (value > 0 && value < 1) { + value = 1; + } + + motionBucketId = value.toInt(); + }); + }, + ), + ), + Text( + motionBucketIdText(motionBucketId), + style: TextStyle( + fontSize: 12, + color: customColors.weakTextColor, + ), + ), + ], + ) + ], + ), + // Seed + EnhancedTextField( + controller: seedController, + customColors: customColors, + labelText: 'Seed', + labelPosition: LabelPosition.left, + showCounter: false, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + hintText: '默认随机', + textDirection: TextDirection.rtl, + ), + ], + ), // 生成按钮 const SizedBox(height: 20), Row( children: [ + if (widget.apiEndpoint == 'image-to-video') + EnhancedButton( + title: showAdvancedOptions + ? AppLocale.simpleMode.getString(context) + : AppLocale.professionalMode.getString(context), + width: 120, + backgroundColor: Colors.transparent, + color: customColors.weakLinkColor, + fontSize: 15, + icon: Icon( + showAdvancedOptions ? Icons.unfold_less : Icons.unfold_more, + color: customColors.weakLinkColor, + size: 15, + ), + onPressed: () { + setState(() { + showAdvancedOptions = !showAdvancedOptions; + }); + }, + ), + if (widget.apiEndpoint == 'image-to-video') + const SizedBox(width: 10), Expanded( flex: 1, child: EnhancedButton( @@ -146,6 +414,16 @@ class _ImageEditDirectScreenState extends State { ); } + String cfgScaleText(double? cfgScale) { + cfgScale ??= 0; + return cfgScale == 0 ? 'Auto' : cfgScale.toStringAsFixed(1); + } + + String motionBucketIdText(int? motionBucketId) { + motionBucketId ??= 0; + return motionBucketId == 0 ? 'Auto' : motionBucketId.toString(); + } + void onGenerate() async { FocusScope.of(context).requestFocus(FocusNode()); HapticFeedbackHelper.mediumImpact(); @@ -157,6 +435,14 @@ class _ImageEditDirectScreenState extends State { var params = {}; + if (cfgScale != null && cfgScale! >= 1) { + params['cfg_scale'] = cfgScale; + } + + if (motionBucketId != null && motionBucketId! >= 1) { + params['motion_bucket_id'] = motionBucketId; + } + final cancelOutside = BotToast.showCustomLoading( toastBuilder: (cancel) { return const LoadingIndicator( @@ -180,16 +466,24 @@ class _ImageEditDirectScreenState extends State { allowClick: false, ); - if (selectedImagePath != null && selectedImagePath!.isNotEmpty) { - final uploadRes = await ImageUploader(widget.setting) - .upload(selectedImagePath!) - .whenComplete(() => cancel()); - params['image'] = uploadRes.url; - } else if (selectedImageData != null && selectedImageData!.isNotEmpty) { - final uploadRes = await ImageUploader(widget.setting) - .uploadData(selectedImageData!) - .whenComplete(() => cancel()); - params['image'] = uploadRes.url; + if (selectedImagePath != null && + (selectedImagePath!.startsWith('http://') || + selectedImagePath!.startsWith('https://'))) { + params['image'] = selectedImagePath; + cancel(); + } else { + if (selectedImagePath != null && selectedImagePath!.isNotEmpty) { + final uploadRes = await ImageUploader(widget.setting) + .upload(selectedImagePath!) + .whenComplete(() => cancel()); + params['image'] = uploadRes.url; + } else if (selectedImageData != null && + selectedImageData!.isNotEmpty) { + final uploadRes = await ImageUploader(widget.setting) + .uploadData(selectedImageData!) + .whenComplete(() => cancel()); + params['image'] = uploadRes.url; + } } final taskId = await APIServer().creativeIslandImageDirectEdit( @@ -228,7 +522,7 @@ class _ImageEditDirectScreenState extends State { } try { - request(30); + request(widget.initWaitDuration); } catch (e) { cancelOutside(); showErrorMessageEnhanced(context, e); @@ -253,6 +547,13 @@ class _ImageEditDirectScreenState extends State { resp.originImage != '') { params['image'] = resp.originImage; } + if (params != null && resp.width != null) { + params['width'] = resp.width; + } + if (params != null && resp.height != null) { + params['height'] = resp.height; + } + return IslandResult( result: resp.resources ?? const [], params: params, diff --git a/lib/page/creative_island/gallery/gallery.dart b/lib/page/creative_island/gallery/gallery.dart index 9c3717be..490d21d5 100644 --- a/lib/page/creative_island/gallery/gallery.dart +++ b/lib/page/creative_island/gallery/gallery.dart @@ -81,7 +81,7 @@ class _GalleryScreenState extends State { ListConfig( itemBuilder: (context, item, index) { return ImageCard( - images: item.images, + images: [item.preview], username: item.username, userId: item.userId, hotValue: item.hotValue, diff --git a/lib/page/creative_island/gallery/gallery_item.dart b/lib/page/creative_island/gallery/gallery_item.dart index 2c61e9f1..538cb085 100644 --- a/lib/page/creative_island/gallery/gallery_item.dart +++ b/lib/page/creative_island/gallery/gallery_item.dart @@ -9,7 +9,9 @@ import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/column_block.dart'; import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/gallery_item_share.dart'; +import 'package:askaide/page/component/image_action.dart'; import 'package:askaide/page/component/image_preview.dart'; +import 'package:askaide/page/component/item_selector_search.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; @@ -211,7 +213,7 @@ class _GalleryItemScreenState extends State { EnhancedButton( title: '分享', icon: const Icon(Icons.share, size: 14), - width: 100, + width: 80, color: customColors.backgroundInvertedColor, backgroundColor: customColors.backgroundColor, @@ -232,6 +234,63 @@ class _GalleryItemScreenState extends State { }, ), const SizedBox(width: 10), + EnhancedButton( + title: '动作', + icon: const Icon(Icons.webhook, size: 14), + width: 80, + color: customColors.backgroundInvertedColor, + backgroundColor: + customColors.backgroundColor, + onPressed: () { + if (state.item.images.length > 1) { + List> items = []; + for (var i = 0; + i < state.item.images.length; + i++) { + items.add(SelectorItem( + NetworkImagePreviewer( + url: state.item.images[i], + notClickable: true, + hidePreviewButton: true, + borderRadius: + BorderRadius.circular(8), + ), + state.item.images[i], + )); + } + openListSelectDialog( + context, + items, + (selected) { + context.pop(); + + openImageWorkflowActionDialog( + context, + customColors, + selected.value, + ); + + return false; + }, + horizontal: true, + horizontalCount: 2, + heightFactor: 0.8, + innerPadding: + const EdgeInsets.symmetric( + vertical: 10, + ), + title: '选择要执行动作的图片', + ); + } else { + openImageWorkflowActionDialog( + context, + customColors, + state.item.images.first, + ); + } + }, + ), + const SizedBox(width: 10), Expanded( child: EnhancedButton( title: '制作同款', diff --git a/lib/page/creative_island/my_creation.dart b/lib/page/creative_island/my_creation.dart index 19432b3d..f7975124 100644 --- a/lib/page/creative_island/my_creation.dart +++ b/lib/page/creative_island/my_creation.dart @@ -300,7 +300,37 @@ class _MyCreationScreenState extends State { BuildContext context, CreativeItemInServer item, ) { - if (item.isImageType && item.images.isNotEmpty) { + if (item.isVideoType && item.originalImage != null) { + return ConstrainedBox( + constraints: const BoxConstraints( + minHeight: 100, + ), + child: Stack( + children: [ + ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(8), + topRight: Radius.circular(8), + ), + child: CachedNetworkImageEnhanced( + imageUrl: + imageURL(item.originalImage!, qiniuImageTypeThumbMedium), + fit: BoxFit.cover, + ), + ), + Positioned( + right: 10, + bottom: 10, + child: Image.asset( + 'assets/play.png', + width: 40, + opacity: const AlwaysStoppedAnimation(0.7), + ), + ), + ], + ), + ); + } else if (item.isImageType && item.images.isNotEmpty) { return ConstrainedBox( constraints: const BoxConstraints( minHeight: 100, diff --git a/lib/page/creative_island/my_creation_item.dart b/lib/page/creative_island/my_creation_item.dart index 997d199c..7833dabf 100644 --- a/lib/page/creative_island/my_creation_item.dart +++ b/lib/page/creative_island/my_creation_item.dart @@ -59,7 +59,8 @@ class _MyCreationItemPageState extends State return BlocBuilder( buildWhen: (previous, current) => - current is CreativeIslandHistoryItemLoaded, + current is CreativeIslandHistoryItemLoaded || + current is CreativeIslandHistoryItemLoading, builder: (context, state) { if (state is CreativeIslandHistoryItemLoaded) { return Scaffold( @@ -144,7 +145,9 @@ class _MyCreationItemPageState extends State ); } - return Container(); + return const Center( + child: CircularProgressIndicator(), + ); }, ); } diff --git a/lib/page/lab/creative_models.dart b/lib/page/lab/creative_models.dart index a099a347..2c2bad1a 100644 --- a/lib/page/lab/creative_models.dart +++ b/lib/page/lab/creative_models.dart @@ -262,10 +262,18 @@ class _CreativeModelScreenState extends State { .startsWith('https://')) ClipRRect( borderRadius: BorderRadius.circular(10), - child: CachedNetworkImageEnhanced( - imageUrl: e.firstImagePreview, - fit: BoxFit.cover, - ), + child: e.firstImagePreview + .endsWith('.mp4') + ? CachedNetworkImageEnhanced( + imageUrl: e.params['image'] ?? + e.firstImagePreview, + fit: BoxFit.cover, + height: double.infinity, + ) + : CachedNetworkImageEnhanced( + imageUrl: e.firstImagePreview, + fit: BoxFit.cover, + ), ) else if (e.isProcessing) Container( @@ -332,7 +340,33 @@ class _CreativeModelScreenState extends State { ), ), ), - ) + ), + if (e.islandName != null) + Positioned( + left: 0, + top: 0, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 5, + ), + decoration: BoxDecoration( + borderRadius: + const BorderRadius.only( + topLeft: Radius.circular(5), + bottomRight: Radius.circular(5), + ), + color: customColors.linkColor, + ), + child: Text( + e.islandName!, + style: const TextStyle( + fontSize: 10, + color: Colors.white, + ), + ), + ), + ) ], ), ), diff --git a/lib/repo/api/creative.dart b/lib/repo/api/creative.dart index e50a0540..2f45560c 100644 --- a/lib/repo/api/creative.dart +++ b/lib/repo/api/creative.dart @@ -39,6 +39,8 @@ class CreativeGallery { DateTime? createdAt; DateTime? updatedAt; + String? previewImage; + CreativeGallery({ required this.id, this.userId, @@ -54,6 +56,7 @@ class CreativeGallery { this.starLevel = 0, this.hotValue = 0, this.status = 0, + this.previewImage, this.createdAt, this.updatedAt, }); @@ -79,6 +82,19 @@ class CreativeGallery { } } + /// 封面图 + String get preview { + if (previewImage != null && previewImage != '') { + return previewImage!; + } + + if (images.isNotEmpty) { + return images.first; + } + + return ''; + } + toJson() => { 'id': id, 'user_id': userId, @@ -94,6 +110,7 @@ class CreativeGallery { 'star_level': starLevel, 'hot_value': hotValue, 'status': status, + 'preview_image': previewImage, 'created_at': createdAt?.toIso8601String(), 'updated_at': updatedAt?.toIso8601String(), }; @@ -114,6 +131,7 @@ class CreativeGallery { starLevel: json['star_level'] ?? 0, hotValue: json['hot_value'] ?? 0, status: json['status'] ?? 0, + previewImage: json['preview_image'], createdAt: json['created_at'] != null ? DateTime.parse(json['created_at']) : null, @@ -665,9 +683,14 @@ class CreativeItemInServer { bool get isImageType => islandType != null && (islandType == 2 || islandType! >= 5); + bool get isVideoType => islandType != null && islandType == 3; + List get images { try { - if (isImageType && answer != null && answer != '' && isSuccessful) { + if ((isImageType || isVideoType) && + answer != null && + answer != '' && + isSuccessful) { return (jsonDecode(answer!) as List).cast(); } return []; @@ -706,6 +729,11 @@ class CreativeItemInServer { return {}; } + /// 上传的原图 + String? get originalImage { + return params['image']; + } + String get markdownAnswer { if (isProcessing) { return '正在生成中...'; diff --git a/lib/repo/api_server.dart b/lib/repo/api_server.dart index 3f50f8a4..cc4f1f2a 100644 --- a/lib/repo/api_server.dart +++ b/lib/repo/api_server.dart @@ -736,6 +736,18 @@ class APIServer { ); } + Future creativeIslandImageToVideoCompletionsAsyncV2( + Map params) async { + return sendPostRequest( + '/v2/creative-island/completions/image-to-video', + (resp) { + final cicResp = CreativeIslandCompletionAsyncResp.fromJson(resp.data); + return cicResp.taskId; + }, + formData: params, + ); + } + Future creativeIslandImageDirectEdit( String endpoint, Map params, diff --git a/lib/repo/model/misc.dart b/lib/repo/model/misc.dart index 48507377..ddbf041a 100644 --- a/lib/repo/model/misc.dart +++ b/lib/repo/model/misc.dart @@ -362,14 +362,19 @@ class AsyncTaskResp { List? errors; List? resources; String? originImage; + int? width; + int? height; - AsyncTaskResp(this.status, {this.errors, this.resources, this.originImage}); + AsyncTaskResp(this.status, + {this.errors, this.resources, this.originImage, this.width, this.height}); toJson() => { 'status': status, 'errors': errors, 'resources': resources, 'origin_image': originImage, + 'width': width, + 'height': height, }; static AsyncTaskResp fromJson(Map json) { @@ -384,6 +389,8 @@ class AsyncTaskResp { .toList() : null, originImage: json['origin_image'], + width: json['width'], + height: json['height'], ); } } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index b6ef9948..eb2616a4 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include @@ -22,6 +24,12 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_localization_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLocalizationPlugin"); flutter_localization_plugin_register_with_registrar(flutter_localization_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) record_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin"); record_linux_plugin_register_with_registrar(record_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index acf1eaeb..c57eb9c1 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -6,11 +6,14 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_linux file_saver flutter_localization + media_kit_libs_linux + media_kit_video record_linux url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + media_kit_native_event_loop ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 3a90c6bd..8d267c74 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -11,13 +11,18 @@ import flutter_local_notifications import flutter_localization import flutter_tts import in_app_purchase_storekit +import media_kit_libs_macos_video +import media_kit_video +import package_info_plus import path_provider_foundation import record_macos +import screen_brightness_macos import share_plus import shared_preferences_foundation import sign_in_with_apple import sqflite import url_launcher_macos +import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) @@ -26,11 +31,16 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin")) FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin")) InAppPurchasePlugin.register(with: registry.registrar(forPlugin: "InAppPurchasePlugin")) + MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) + MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) RecordMacosPlugin.register(with: registry.registrar(forPlugin: "RecordMacosPlugin")) + ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SignInWithApplePlugin.register(with: registry.registrar(forPlugin: "SignInWithApplePlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) } diff --git a/macos/Podfile.lock b/macos/Podfile.lock index f9abccc5..f946cc5d 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -16,11 +16,21 @@ PODS: - in_app_purchase_storekit (0.0.1): - Flutter - FlutterMacOS + - media_kit_libs_macos_video (1.0.4): + - FlutterMacOS + - media_kit_native_event_loop (1.0.0): + - FlutterMacOS + - media_kit_video (0.0.1): + - FlutterMacOS + - package_info_plus (0.0.1): + - FlutterMacOS - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - record_macos (0.2.0): - FlutterMacOS + - screen_brightness_macos (0.1.0): + - FlutterMacOS - share_plus (0.0.1): - FlutterMacOS - shared_preferences_foundation (0.0.1): @@ -33,6 +43,8 @@ PODS: - FMDB (>= 2.7.5) - url_launcher_macos (0.0.1): - FlutterMacOS + - wakelock_plus (0.0.1): + - FlutterMacOS DEPENDENCIES: - audioplayers_darwin (from `Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos`) @@ -42,13 +54,19 @@ DEPENDENCIES: - flutter_tts (from `Flutter/ephemeral/.symlinks/plugins/flutter_tts/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - in_app_purchase_storekit (from `Flutter/ephemeral/.symlinks/plugins/in_app_purchase_storekit/darwin`) + - media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`) + - media_kit_native_event_loop (from `Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos`) + - media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`) + - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - record_macos (from `Flutter/ephemeral/.symlinks/plugins/record_macos/macos`) + - screen_brightness_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos`) - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - sign_in_with_apple (from `Flutter/ephemeral/.symlinks/plugins/sign_in_with_apple/macos`) - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + - wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`) SPEC REPOS: trunk: @@ -69,10 +87,20 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral in_app_purchase_storekit: :path: Flutter/ephemeral/.symlinks/plugins/in_app_purchase_storekit/darwin + media_kit_libs_macos_video: + :path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos + media_kit_native_event_loop: + :path: Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos + media_kit_video: + :path: Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos + package_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin record_macos: :path: Flutter/ephemeral/.symlinks/plugins/record_macos/macos + screen_brightness_macos: + :path: Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos share_plus: :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos shared_preferences_foundation: @@ -83,6 +111,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + wakelock_plus: + :path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos SPEC CHECKSUMS: audioplayers_darwin: dcad41de4fbd0099cb3749f7ab3b0cb8f70b810c @@ -93,13 +123,19 @@ SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a in_app_purchase_storekit: 4fb7ee9e824b1f09107fbfbbce8c4b276366dc43 + media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82 + media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5 + media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5 + package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 record_macos: 937889e0f2a7a12b6fc14e97a3678e5a18943de6 + screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 sign_in_with_apple: a9e97e744e8edc36aefc2723111f652102a7a727 sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 + wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269 PODFILE CHECKSUM: 38f6b47b8cb10a0771c5d71f5d300e8d2bb9b8d7 diff --git a/pubspec.lock b/pubspec.lock index 9c4e9964..978822b3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -487,10 +487,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: b85eb92b175767fdaa0c543bf3b0d1f610fe966412ea72845fe5ba7801e763ff + sha256: be325344c1f3070354a1d84a231a1ba75ea85d413774ec4bdf444c023342e030 url: "https://pub.dev" source: hosted - version: "5.2.10" + version: "5.5.0" file_saver: dependency: "direct main" description: @@ -993,6 +993,78 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.0" + media_kit: + dependency: "direct main" + description: + name: media_kit + sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a" + url: "https://pub.dev" + source: hosted + version: "1.1.10+1" + media_kit_libs_android_video: + dependency: transitive + description: + name: media_kit_libs_android_video + sha256: "9dd8012572e4aff47516e55f2597998f0a378e3d588d0fad0ca1f11a53ae090c" + url: "https://pub.dev" + source: hosted + version: "1.3.6" + media_kit_libs_ios_video: + dependency: transitive + description: + name: media_kit_libs_ios_video + sha256: b5382994eb37a4564c368386c154ad70ba0cc78dacdd3fb0cd9f30db6d837991 + url: "https://pub.dev" + source: hosted + version: "1.1.4" + media_kit_libs_linux: + dependency: transitive + description: + name: media_kit_libs_linux + sha256: e186891c31daa6bedab4d74dcdb4e8adfccc7d786bfed6ad81fe24a3b3010310 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + media_kit_libs_macos_video: + dependency: transitive + description: + name: media_kit_libs_macos_video + sha256: f26aa1452b665df288e360393758f84b911f70ffb3878032e1aabba23aa1032d + url: "https://pub.dev" + source: hosted + version: "1.1.4" + media_kit_libs_video: + dependency: "direct main" + description: + name: media_kit_libs_video + sha256: "3688e0c31482074578652bf038ce6301a5d21e1eda6b54fc3117ffeb4bdba067" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + media_kit_libs_windows_video: + dependency: transitive + description: + name: media_kit_libs_windows_video + sha256: "7bace5f35d9afcc7f9b5cdadb7541d2191a66bb3fc71bfa11c1395b3360f6122" + url: "https://pub.dev" + source: hosted + version: "1.0.9" + media_kit_native_event_loop: + dependency: transitive + description: + name: media_kit_native_event_loop + sha256: a605cf185499d14d58935b8784955a92a4bf0ff4e19a23de3d17a9106303930e + url: "https://pub.dev" + source: hosted + version: "1.0.8" + media_kit_video: + dependency: "direct main" + description: + name: media_kit_video + sha256: c048d11a19e379aebbe810647636e3fc6d18374637e2ae12def4ff8a4b99a882 + url: "https://pub.dev" + source: hosted + version: "1.2.4" meta: dependency: transitive description: @@ -1041,6 +1113,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + url: "https://pub.dev" + source: hosted + version: "2.0.1" path: dependency: transitive description: @@ -1282,6 +1370,62 @@ 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" + screen_brightness: + dependency: transitive + description: + name: screen_brightness + sha256: ed8da4a4511e79422fc1aa88138e920e4008cd312b72cdaa15ccb426c0faaedd + url: "https://pub.dev" + source: hosted + version: "0.2.2+1" + screen_brightness_android: + dependency: transitive + description: + name: screen_brightness_android + sha256: "3df10961e3a9e968a5e076fe27e7f4741fa8a1d3950bdeb48cf121ed529d0caf" + url: "https://pub.dev" + source: hosted + version: "0.1.0+2" + screen_brightness_ios: + dependency: transitive + description: + name: screen_brightness_ios + sha256: "99adc3ca5490b8294284aad5fcc87f061ad685050e03cf45d3d018fe398fd9a2" + url: "https://pub.dev" + source: hosted + version: "0.1.0" + screen_brightness_macos: + dependency: transitive + description: + name: screen_brightness_macos + sha256: "64b34e7e3f4900d7687c8e8fb514246845a73ecec05ab53483ed025bd4a899fd" + url: "https://pub.dev" + source: hosted + version: "0.1.0+1" + screen_brightness_platform_interface: + dependency: transitive + description: + name: screen_brightness_platform_interface + sha256: b211d07f0c96637a15fb06f6168617e18030d5d74ad03795dd8547a52717c171 + url: "https://pub.dev" + source: hosted + version: "0.1.0" + screen_brightness_windows: + dependency: transitive + description: + name: screen_brightness_windows + sha256: "9261bf33d0fc2707d8cf16339ce25768100a65e70af0fcabaf032fc12408ba86" + url: "https://pub.dev" + source: hosted + version: "0.1.3" scroll_to_index: dependency: transitive description: @@ -1302,18 +1446,18 @@ packages: dependency: "direct main" description: name: share_plus - sha256: b1f15232d41e9701ab2f04181f21610c36c83a12ae426b79b4bd011c567934b1 + sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd url: "https://pub.dev" source: hosted - version: "6.3.4" + version: "7.2.1" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "357412af4178d8e11d14f41723f80f12caea54cf0d5cd29af9dcdab85d58aea7" + sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956 url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.3.1" shared_preferences: dependency: transitive description: @@ -1663,6 +1807,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.2" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc + url: "https://pub.dev" + source: hosted + version: "1.0.0+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: @@ -1791,6 +1951,30 @@ packages: url: "https://pub.dev" source: hosted version: "11.10.0" + volume_controller: + dependency: transitive + description: + name: volume_controller + sha256: "189bdc7a554f476b412e4c8b2f474562b09d74bc458c23667356bce3ca1d48c9" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + wakelock_plus: + dependency: transitive + description: + name: wakelock_plus + sha256: f268ca2116db22e57577fb99d52515a24bdc1d570f12ac18bb762361d43b043d + url: "https://pub.dev" + source: hosted + version: "1.1.4" + wakelock_plus_platform_interface: + dependency: transitive + description: + name: wakelock_plus_platform_interface + sha256: "40fabed5da06caff0796dc638e1f07ee395fb18801fbff3255a2372db2d80385" + url: "https://pub.dev" + source: hosted + version: "1.1.0" watcher: dependency: transitive description: @@ -1843,10 +2027,10 @@ packages: dependency: transitive description: name: win32 - sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 + sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "5.1.1" xdg_directories: dependency: transitive description: @@ -1872,5 +2056,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" + dart: ">=3.2.0 <4.0.0" flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index 85ea6e43..49f7e4f3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. # 应用正式发布时,需要同步修改 lib/helper/constant.dart 中的 VERSION 值 -version: 1.0.10+1 +version: 1.0.11+1 environment: sdk: '>=3.0.0 <4.0.0' @@ -55,7 +55,7 @@ dependencies: git: url: https://github.com/mylxsw/FlutterIconPicker ref: hotfix - share_plus: ^6.3.1 + share_plus: ^7.2.1 go_router: ^8.2.0 sqflite: ^2.2.6 file_picker: ^5.2.8 @@ -118,6 +118,9 @@ dependencies: flutter_initicon: ^3.0.0+1 markdown_widget: ^2.3.1 flutter_math_fork: ^0.7.2 + media_kit: ^1.1.10 + media_kit_video: ^1.2.4 + media_kit_libs_video: ^1.0.4 dev_dependencies: flutter_test: @@ -180,6 +183,8 @@ flutter: - assets/github.png - assets/x.png - assets/xiaohongshu.png + - assets/play.png + - assets/text-to-video.gif # - images/a_dot_ham.jpeg diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 46b893cd..7593725b 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -10,7 +10,10 @@ #include #include #include +#include +#include #include +#include #include #include @@ -23,8 +26,14 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi")); FlutterTtsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterTtsPlugin")); + MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); + MediaKitVideoPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi")); RecordWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("RecordWindowsPluginCApi")); + ScreenBrightnessWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 1725e438..0eac2e67 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -7,12 +7,16 @@ list(APPEND FLUTTER_PLUGIN_LIST file_saver flutter_localization flutter_tts + media_kit_libs_windows_video + media_kit_video record_windows + screen_brightness_windows share_plus url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + media_kit_native_event_loop ) set(PLUGIN_BUNDLED_LIBRARIES)