diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9116946..bfb58c6 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,33 +1,92 @@ PODS: + - DKImagePickerController/Core (4.3.9): + - DKImagePickerController/ImageDataManager + - DKImagePickerController/Resource + - DKImagePickerController/ImageDataManager (4.3.9) + - DKImagePickerController/PhotoGallery (4.3.9): + - DKImagePickerController/Core + - DKPhotoGallery + - DKImagePickerController/Resource (4.3.9) + - DKPhotoGallery (0.0.19): + - DKPhotoGallery/Core (= 0.0.19) + - DKPhotoGallery/Model (= 0.0.19) + - DKPhotoGallery/Preview (= 0.0.19) + - DKPhotoGallery/Resource (= 0.0.19) + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Core (0.0.19): + - DKPhotoGallery/Model + - DKPhotoGallery/Preview + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Model (0.0.19): + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Preview (0.0.19): + - DKPhotoGallery/Model + - DKPhotoGallery/Resource + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Resource (0.0.19): + - SDWebImage + - SwiftyGif + - file_picker (0.0.1): + - DKImagePickerController/PhotoGallery + - Flutter - Flutter (1.0.0) - flutter_secure_storage (6.0.0): - Flutter + - image_picker_ios (0.0.1): + - Flutter - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS + - SDWebImage (5.19.2): + - SDWebImage/Core (= 5.19.2) + - SDWebImage/Core (5.19.2) + - SwiftyGif (5.4.5) - url_launcher_ios (0.0.1): - Flutter DEPENDENCIES: + - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) +SPEC REPOS: + trunk: + - DKImagePickerController + - DKPhotoGallery + - SDWebImage + - SwiftyGif + EXTERNAL SOURCES: + file_picker: + :path: ".symlinks/plugins/file_picker/ios" Flutter: :path: Flutter flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: + DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c + DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 + file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 + image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + SDWebImage: dfe95b2466a9823cf9f0c6d01217c06550d7b29a + SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index dc91edf..cd17c2a 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -28,6 +28,12 @@ LaunchScreen UIMainStoryboardFile Main + NSPhotoLibraryUsageDescription + Allow you add photo to your message or post + NSCameraUsageDescription + Allow you take photo/video for your message or post + NSMicrophoneUsageDescription + Allow you record audio for your message or post UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/lib/exts.dart b/lib/exts.dart new file mode 100644 index 0000000..e04d5fb --- /dev/null +++ b/lib/exts.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +extension SolianExtenions on BuildContext { + void showSnackbar(String content) { + ScaffoldMessenger.of(this).showSnackBar(SnackBar( + content: Text(content), + )); + } + + Future showErrorDialog(dynamic exception) { + String formatMessage(dynamic exception) { + final message = exception.toString(); + if (message.trim().isEmpty) return ''; + return message + .split(' ') + .map((element) => '${element[0].toUpperCase()}${element.substring(1).toLowerCase()}') + .join(' '); + } + + return showDialog( + context: this, + builder: (ctx) => AlertDialog( + title: Text('errorHappened'.tr), + content: Text(formatMessage(exception)), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + child: Text('okay'.tr), + ) + ], + ), + ); + } +} diff --git a/lib/screens/auth/signin.dart b/lib/screens/auth/signin.dart index 93f1d4c..6f70866 100644 --- a/lib/screens/auth/signin.dart +++ b/lib/screens/auth/signin.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:solian/exts.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/router.dart'; import 'package:solian/services.dart'; @@ -29,7 +30,7 @@ class _SignInScreenState extends State { if (messages.last.contains('risk')) { final ticketId = RegExp(r'ticketId=(\d+)').firstMatch(messages.last); if (ticketId == null) { - Get.snackbar('errorHappened'.tr, 'Requested to multi-factor authenticate, but the ticket id was not found'); + context.showErrorDialog('Requested to multi-factor authenticate, but the ticket id was not found'); } showDialog( context: context, diff --git a/lib/screens/auth/signup.dart b/lib/screens/auth/signup.dart index fb8afde..044ec6b 100644 --- a/lib/screens/auth/signup.dart +++ b/lib/screens/auth/signup.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:solian/exts.dart'; import 'package:solian/router.dart'; import 'package:solian/services.dart'; @@ -54,7 +55,7 @@ class _SignUpScreenState extends State { AppRouter.instance.replaceNamed('auth.sign-in'); }); } else { - Get.snackbar('errorHappened'.tr, resp.bodyString!); + context.showErrorDialog(resp.bodyString); } } diff --git a/lib/screens/posts/publish.dart b/lib/screens/posts/publish.dart index 3962d06..d337992 100644 --- a/lib/screens/posts/publish.dart +++ b/lib/screens/posts/publish.dart @@ -1,11 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:get/get.dart'; +import 'package:solian/exts.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/router.dart'; import 'package:solian/services.dart'; import 'package:solian/widgets/account/account_avatar.dart'; import 'package:solian/shells/nav_shell.dart' as shell; +import 'package:solian/widgets/attachments/attachment_publish.dart'; class PostPublishingScreen extends StatefulWidget { const PostPublishingScreen({super.key}); @@ -19,6 +21,19 @@ class _PostPublishingScreenState extends State { bool _isSubmitting = false; + List _attachments = List.empty(); + + void showAttachments(BuildContext context) { + showModalBottomSheet( + context: context, + builder: (context) => AttachmentPublishingPopup( + usage: 'i.attachment', + current: _attachments, + onUpdate: (value) => _attachments = value, + ), + ); + } + void applyPost() async { final AuthProvider auth = Get.find(); if (!await auth.isAuthorized) return; @@ -32,9 +47,10 @@ class _PostPublishingScreenState extends State { final resp = await client.post('/api/posts', { 'content': _contentController.value.text, + 'attachments': _attachments, }); if (resp.statusCode != 200) { - Get.snackbar('errorHappened'.tr, resp.bodyString!); + context.showErrorDialog(resp.bodyString); } else { AppRouter.instance.pop(resp.body); } @@ -107,7 +123,7 @@ class _PostPublishingScreenState extends State { TextButton( style: TextButton.styleFrom(shape: const CircleBorder()), child: const Icon(Icons.camera_alt), - onPressed: () {}, + onPressed: () => showAttachments(context), ) ], ), diff --git a/lib/translations.dart b/lib/translations.dart index 83b5e86..a0925bf 100644 --- a/lib/translations.dart +++ b/lib/translations.dart @@ -4,6 +4,7 @@ class SolianMessages extends Translations { @override Map> get keys => { 'en_US': { + 'okay': 'Okay', 'next': 'Next', 'page': 'Page', 'home': 'Home', @@ -32,9 +33,16 @@ class SolianMessages extends Translations { 'postReaction': 'Reactions of the Post', 'reactAdd': 'React', 'reactCompleted': 'Your reaction has been added', - 'reactUncompleted': 'Your reaction has been removed' + 'reactUncompleted': 'Your reaction has been removed', + 'attachmentAdd': 'Attach attachments', + 'attachmentAddGalleryPhoto': 'Gallery photo', + 'attachmentAddGalleryVideo': 'Gallery video', + 'attachmentAddCameraPhoto': 'Capture photo', + 'attachmentAddCameraVideo': 'Capture video', + 'attachmentAddFile': 'Attach file', }, 'zh_CN': { + 'okay': '确认', 'next': '下一步', 'page': '页面', 'home': '首页', @@ -62,7 +70,13 @@ class SolianMessages extends Translations { 'postReaction': '帖子的反应', 'reactAdd': '作出反应', 'reactCompleted': '你的反应已被添加', - 'reactUncompleted': '你的反应已被移除' + 'reactUncompleted': '你的反应已被移除', + 'attachmentAdd': '附加附件', + 'attachmentAddGalleryPhoto': '相册照片', + 'attachmentAddGalleryVideo': '相册视频', + 'attachmentAddCameraPhoto': '拍摄图片', + 'attachmentAddCameraVideo': '拍摄视频', + 'attachmentAddFile': '附加文件', } }; } diff --git a/lib/widgets/attachments/attachment_publish.dart b/lib/widgets/attachments/attachment_publish.dart new file mode 100644 index 0000000..9c3f7bf --- /dev/null +++ b/lib/widgets/attachments/attachment_publish.dart @@ -0,0 +1,315 @@ +import 'dart:io'; +import 'dart:math' as math; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import 'package:get/get.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:path/path.dart'; +import 'package:solian/exts.dart'; +import 'package:solian/models/attachment.dart'; +import 'package:solian/providers/auth.dart'; +import 'package:crypto/crypto.dart'; +import 'package:solian/services.dart'; + +Future calculateFileSha256(File file) async { + final bytes = await file.readAsBytes(); + final digest = sha256.convert(bytes); + return digest.toString(); +} + +class AttachmentPublishingPopup extends StatefulWidget { + final String usage; + final List current; + final void Function(List data) onUpdate; + + const AttachmentPublishingPopup({ + super.key, + required this.usage, + required this.current, + required this.onUpdate, + }); + + @override + State createState() => _AttachmentPublishingPopupState(); +} + +class _AttachmentPublishingPopupState extends State { + final _imagePicker = ImagePicker(); + + bool _isSubmitting = false; + + final List _attachments = List.empty(growable: true); + + Future pickPhotoToUpload() async { + final AuthProvider auth = Get.find(); + if (!await auth.isAuthorized) return; + + final medias = await _imagePicker.pickMultiImage(); + if (medias.isEmpty) return; + + setState(() => _isSubmitting = true); + + for (final media in medias) { + final file = File(media.path); + final hash = await calculateFileSha256(file); + final image = await decodeImageFromList(await file.readAsBytes()); + final ratio = image.width / image.height; + + try { + await uploadAttachment(file, hash, ratio: ratio); + } catch (err) { + this.context.showErrorDialog(err); + } + } + + setState(() => _isSubmitting = false); + } + + Future pickVideoToUpload() async { + final AuthProvider auth = Get.find(); + if (!await auth.isAuthorized) return; + + final media = await _imagePicker.pickVideo(source: ImageSource.gallery); + if (media == null) return; + + setState(() => _isSubmitting = true); + + final file = File(media.path); + final hash = await calculateFileSha256(file); + const ratio = 16 / 9; // TODO Calculate ratio of video + + try { + await uploadAttachment(file, hash, ratio: ratio); + } catch (err) { + this.context.showErrorDialog(err); + } + + setState(() => _isSubmitting = false); + } + + Future pickFileToUpload() async { + final AuthProvider auth = Get.find(); + if (!await auth.isAuthorized) return; + + FilePickerResult? result = await FilePicker.platform.pickFiles(allowMultiple: true); + if (result == null) return; + + List files = result.paths.map((path) => File(path!)).toList(); + + setState(() => _isSubmitting = true); + + for (final file in files) { + final hash = await calculateFileSha256(file); + try { + await uploadAttachment(file, hash); + } catch (err) { + this.context.showErrorDialog(err); + } + } + + setState(() => _isSubmitting = false); + } + + Future takeMediaToUpload(bool isVideo) async { + final AuthProvider auth = Get.find(); + if (!await auth.isAuthorized) return; + + XFile? media; + if (isVideo) { + media = await _imagePicker.pickVideo(source: ImageSource.camera); + } else { + media = await _imagePicker.pickImage(source: ImageSource.camera); + } + if (media == null) return; + + setState(() => _isSubmitting = true); + + double? ratio; + final file = File(media.path); + final hash = await calculateFileSha256(file); + + if (isVideo) { + // TODO Calculate ratio of video + ratio = 16 / 9; + } else { + final image = await decodeImageFromList(await file.readAsBytes()); + ratio = image.width / image.height; + } + + try { + await uploadAttachment(file, hash, ratio: ratio); + } catch (err) { + this.context.showErrorDialog(err); + } + + setState(() => _isSubmitting = false); + } + + Future uploadAttachment(File file, String hash, {double? ratio}) async { + final AuthProvider auth = Get.find(); + + final client = GetConnect(); + client.httpClient.baseUrl = ServiceFinder.services['paperclip']; + client.httpClient.addAuthenticator(auth.reqAuthenticator); + + final filePayload = MultipartFile(await file.readAsBytes(), filename: basename(file.path)); + final fileAlt = basename(file.path).contains('.') + ? basename(file.path).substring(0, basename(file.path).lastIndexOf('.')) + : basename(file.path); + + final resp = await client.post( + '/api/attachments', + FormData({ + 'alt': fileAlt, + 'file': filePayload, + 'hash': hash, + 'usage': widget.usage, + 'metadata': { + if (ratio != null) 'ratio': ratio, + }, + }), + ); + if (resp.statusCode == 200) { + var result = Attachment.fromJson(resp.body); + setState(() => _attachments.add(result)); + widget.onUpdate(_attachments.map((e) => e.uuid).toList()); + } else { + throw Exception(resp.bodyString); + } + } + + Future disposeAttachment(Attachment item, int index) async { + final AuthProvider auth = Get.find(); + + final client = GetConnect(); + client.httpClient.baseUrl = ServiceFinder.services['paperclip']; + client.httpClient.addAuthenticator(auth.reqAuthenticator); + + setState(() => _isSubmitting = true); + var resp = await client.delete('/api/attachments/${item.id}'); + if (resp.statusCode == 200) { + setState(() => _attachments.removeAt(index)); + widget.onUpdate(_attachments.map((e) => e.uuid).toList()); + } else { + this.context.showErrorDialog(resp.bodyString); + } + setState(() => _isSubmitting = false); + } + + String formatBytes(int bytes, {int decimals = 2}) { + if (bytes == 0) return '0 Bytes'; + const k = 1024; + final dm = decimals < 0 ? 0 : decimals; + final sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; + final i = (math.log(bytes) / math.log(k)).floor().toInt(); + return '${(bytes / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}'; + } + + @override + Widget build(BuildContext context) { + const density = VisualDensity(horizontal: 0, vertical: 0); + + return SafeArea( + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.85, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'attachmentAdd'.tr, + style: Theme.of(context).textTheme.headlineSmall, + ).paddingOnly(left: 24, right: 24, top: 32, bottom: 16), + _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(), + Expanded( + child: ListView.builder( + itemCount: _attachments.length, + itemBuilder: (context, index) { + final element = _attachments[index]; + final fileType = element.mimetype.split('/').first; + return Container( + padding: const EdgeInsets.only(left: 16, right: 8, bottom: 16), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + element.alt, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + Text( + '${fileType[0].toUpperCase()}${fileType.substring(1)} · ${formatBytes(element.size)}', + ), + ], + ), + ), + TextButton( + style: TextButton.styleFrom( + shape: const CircleBorder(), + foregroundColor: Colors.red, + ), + child: const Icon(Icons.delete), + onPressed: () => disposeAttachment(element, index), + ), + ], + ), + ); + }, + ), + ), + const Divider(thickness: 0.3, height: 0.3), + SizedBox( + height: 64, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Wrap( + spacing: 8, + runSpacing: 0, + alignment: WrapAlignment.center, + runAlignment: WrapAlignment.center, + children: [ + ElevatedButton.icon( + icon: const Icon(Icons.add_photo_alternate), + label: Text('attachmentAddGalleryPhoto'.tr), + style: const ButtonStyle(visualDensity: density), + onPressed: () => pickPhotoToUpload(), + ), + ElevatedButton.icon( + icon: const Icon(Icons.add_road), + label: Text('attachmentAddGalleryVideo'.tr), + style: const ButtonStyle(visualDensity: density), + onPressed: () => pickVideoToUpload(), + ), + ElevatedButton.icon( + icon: const Icon(Icons.photo_camera_back), + label: Text('attachmentAddCameraPhoto'.tr), + style: const ButtonStyle(visualDensity: density), + onPressed: () => takeMediaToUpload(false), + ), + ElevatedButton.icon( + icon: const Icon(Icons.video_camera_back_outlined), + label: Text('attachmentAddCameraVideo'.tr), + style: const ButtonStyle(visualDensity: density), + onPressed: () => takeMediaToUpload(true), + ), + ElevatedButton.icon( + icon: const Icon(Icons.file_present_rounded), + label: Text('attachmentAddFile'.tr), + style: const ButtonStyle(visualDensity: density), + onPressed: () => pickFileToUpload(), + ), + ], + ).paddingSymmetric(horizontal: 12), + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/widgets/posts/post_action.dart b/lib/widgets/posts/post_action.dart index 976b8d0..7649897 100644 --- a/lib/widgets/posts/post_action.dart +++ b/lib/widgets/posts/post_action.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:solian/exts.dart'; import 'package:solian/models/post.dart'; import 'package:solian/models/reaction.dart'; import 'package:solian/providers/auth.dart'; @@ -52,12 +53,12 @@ class _PostQuickActionState extends State { }); if (resp.statusCode == 201) { widget.onReact(symbol, 1); - Get.snackbar('', 'reactCompleted'.tr); + context.showSnackbar('reactCompleted'.tr); } else if (resp.statusCode == 204) { widget.onReact(symbol, -1); - Get.snackbar('', 'reactUncompleted'.tr); + context.showSnackbar('reactUncompleted'.tr); } else { - Get.snackbar('errorHappened'.tr, resp.bodyString!); + context.showErrorDialog(resp.bodyString); } setState(() => _isSubmitting = false); diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 38dd0bc..3ccd551 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,10 +6,14 @@ #include "generated_plugin_registrant.h" +#include #include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 65240e9..9ce94c4 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux flutter_secure_storage_linux url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 5efd5bd..3e0199c 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,11 +5,13 @@ import FlutterMacOS import Foundation +import file_selector_macos import flutter_secure_storage_macos import path_provider_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 27ce763..691607f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -57,8 +57,16 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" - crypto: + cross_file: dependency: transitive + description: + name: cross_file + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + url: "https://pub.dev" + source: hosted + version: "0.3.4+1" + crypto: + dependency: "direct main" description: name: crypto sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab @@ -89,6 +97,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: "29c90806ac5f5fb896547720b73b17ee9aed9bba540dc5d91fe29f8c5745b10a" + url: "https://pub.dev" + source: hosted + version: "8.0.3" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.dev" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + url: "https://pub.dev" + source: hosted + version: "0.9.3+1" flutter: dependency: "direct main" description: flutter @@ -118,6 +166,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.1" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f" + url: "https://pub.dev" + source: hosted + version: "2.0.19" flutter_secure_storage: dependency: "direct main" description: @@ -224,6 +280,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "33974eca2e87e8b4e3727f1b94fa3abcb25afe80b6bc2c4d449a0e150aedf720" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "0f57fee1e8bfadf8cc41818bbcd7f72e53bb768a54d9496355d5e8a5681a19f1" + url: "https://pub.dev" + source: hosted + version: "0.8.12+1" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "5d6eb13048cd47b60dbf1a5495424dea226c5faf3950e20bf8120a58efb5b5f3" + url: "https://pub.dev" + source: hosted + version: "3.0.4" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "4824d8c7f6f89121ef0122ff79bb00b009607faecc8545b86bca9ab5ce1e95bf" + url: "https://pub.dev" + source: hosted + version: "0.8.11+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + url: "https://pub.dev" + source: hosted + version: "2.10.0" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" infinite_scroll_pagination: dependency: "direct main" description: @@ -320,6 +440,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.14.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" oauth2: dependency: "direct main" description: @@ -329,7 +457,7 @@ packages: source: hosted version: "2.0.2" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" diff --git a/pubspec.yaml b/pubspec.yaml index 75d969b..ed82834 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,6 +45,10 @@ dependencies: carousel_slider: ^4.2.1 url_launcher: ^6.2.6 infinite_scroll_pagination: ^4.0.0 + image_picker: ^1.1.1 + file_picker: ^8.0.3 + crypto: ^3.0.3 + path: ^1.9.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 2048c45..602b168 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,10 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index de626cc..b918cf8 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows flutter_secure_storage_windows url_launcher_windows )