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
)