✨ Edit profile
This commit is contained in:
		| @@ -11,6 +11,7 @@ | ||||
|   "screenAccountPublishers": "Publishers", | ||||
|   "screenAccountPublisherNew": "New Publisher", | ||||
|   "screenAccountPublisherEdit": "Edit Publisher", | ||||
|   "screenAccountProfileEdit": "Edit Profile", | ||||
|   "dialogOkay": "Okay", | ||||
|   "dialogCancel": "Cancel", | ||||
|   "dialogConfirm": "Confirm", | ||||
| @@ -36,6 +37,10 @@ | ||||
|   "fieldDescription": "Description", | ||||
|   "fieldUsernameCannotEditHint": "Username cannot be edited after created", | ||||
|   "fieldUsernameLookupHint": "You can use username, phone number or email to login", | ||||
|   "fieldFirstName": "First name", | ||||
|   "fieldLastName": "Last name", | ||||
|   "fieldBirthday": "Birthday", | ||||
|   "fieldImageHint": "You can click those profile pictures to edit them.", | ||||
|   "forgotPassword": "Forgot password", | ||||
|   "loginPickFactor": "Pick a factor", | ||||
|   "loginMultiFactor": { | ||||
| @@ -54,6 +59,9 @@ | ||||
|   "accountLogoutConfirm": "You will need to re-enter your account password, even if you have already done so. This is required to login again.", | ||||
|   "accountPublishers": "Your publishers", | ||||
|   "accountPublishersSubtitle": "Manage your publish identities.", | ||||
|   "accountProfileEdit": "Edit your profile", | ||||
|   "accountProfileEditSubtitle": "Make your Solarpass account more looks like you.", | ||||
|   "accountProfileEditApplied": "Profile modification applied.", | ||||
|   "publishersNew": "New Publisher", | ||||
|   "publisherNewSubtitle": "Create a new publisher identity.", | ||||
|   "publisherSyncWithAccount": "Sync with account" | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
|   "screenAccountPublishers": "发布者", | ||||
|   "screenAccountPublisherNew": "新建发布者", | ||||
|   "screenAccountPublisherEdit": "编辑发布者", | ||||
|   "screenAccountProfileEdit": "编辑资料", | ||||
|   "dialogOkay": "好的", | ||||
|   "dialogCancel": "取消", | ||||
|   "dialogConfirm": "确认", | ||||
| @@ -35,6 +36,10 @@ | ||||
|   "fieldPassword": "密码", | ||||
|   "fieldUsernameCannotEditHint": "用户名在创建后无法修改", | ||||
|   "fieldUsernameLookupHint": "支持用户名、电话号码或邮箱地址", | ||||
|   "fieldFirstName": "名", | ||||
|   "fieldLastName": "姓", | ||||
|   "fieldBirthday": "生日", | ||||
|   "fieldImageHint": "你可以点击这些个人头像来编辑它们。", | ||||
|   "fieldDescription": "简介", | ||||
|   "forgotPassword": "忘记密码", | ||||
|   "loginPickFactor": "选择方式验证", | ||||
| @@ -54,6 +59,9 @@ | ||||
|   "accountLogoutConfirm": "您需要重新输入账号密码,甚至可能需要多步验证来再次登陆。", | ||||
|   "accountPublishers": "你的发布者", | ||||
|   "accountPublishersSubtitle": "管理你的公共形象。", | ||||
|   "accountProfileEdit": "编辑资料", | ||||
|   "accountProfileEditSubtitle": "使你的 Solarpass 账户更像你。", | ||||
|   "accountProfileEditApplied": "个人资料修改已被应用。", | ||||
|   "publishersNew": "新发布者", | ||||
|   "publisherNewSubtitle": "创建一个新的公共身份。", | ||||
|   "publisherSyncWithAccount": "同步账户信息" | ||||
|   | ||||
| @@ -2,47 +2,134 @@ PODS: | ||||
|   - connectivity_plus (0.0.1): | ||||
|     - Flutter | ||||
|     - FlutterMacOS | ||||
|   - croppy (0.0.1): | ||||
|     - Flutter | ||||
|   - cupertino_http (0.0.1): | ||||
|     - Flutter | ||||
|   - 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_image_compress_common (1.0.0): | ||||
|     - Flutter | ||||
|     - Mantle | ||||
|     - SDWebImage | ||||
|     - SDWebImageWebPCoder | ||||
|   - flutter_native_splash (0.0.1): | ||||
|     - Flutter | ||||
|   - flutter_secure_storage (3.3.1): | ||||
|     - Flutter | ||||
|   - image_picker_ios (0.0.1): | ||||
|     - Flutter | ||||
|   - libwebp (1.3.2): | ||||
|     - libwebp/demux (= 1.3.2) | ||||
|     - libwebp/mux (= 1.3.2) | ||||
|     - libwebp/sharpyuv (= 1.3.2) | ||||
|     - libwebp/webp (= 1.3.2) | ||||
|   - libwebp/demux (1.3.2): | ||||
|     - libwebp/webp | ||||
|   - libwebp/mux (1.3.2): | ||||
|     - libwebp/demux | ||||
|   - libwebp/sharpyuv (1.3.2) | ||||
|   - libwebp/webp (1.3.2): | ||||
|     - libwebp/sharpyuv | ||||
|   - Mantle (2.2.0): | ||||
|     - Mantle/extobjc (= 2.2.0) | ||||
|   - Mantle/extobjc (2.2.0) | ||||
|   - path_provider_foundation (0.0.1): | ||||
|     - Flutter | ||||
|     - FlutterMacOS | ||||
|   - SDWebImage (5.19.7): | ||||
|     - SDWebImage/Core (= 5.19.7) | ||||
|   - SDWebImage/Core (5.19.7) | ||||
|   - SDWebImageWebPCoder (0.14.6): | ||||
|     - libwebp (~> 1.0) | ||||
|     - SDWebImage/Core (~> 5.17) | ||||
|   - shared_preferences_foundation (0.0.1): | ||||
|     - Flutter | ||||
|     - FlutterMacOS | ||||
|   - sqflite_darwin (0.0.4): | ||||
|     - Flutter | ||||
|     - FlutterMacOS | ||||
|   - SwiftyGif (5.4.5) | ||||
|   - url_launcher_ios (0.0.1): | ||||
|     - Flutter | ||||
|  | ||||
| DEPENDENCIES: | ||||
|   - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) | ||||
|   - croppy (from `.symlinks/plugins/croppy/ios`) | ||||
|   - cupertino_http (from `.symlinks/plugins/cupertino_http/ios`) | ||||
|   - file_picker (from `.symlinks/plugins/file_picker/ios`) | ||||
|   - Flutter (from `Flutter`) | ||||
|   - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`) | ||||
|   - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) | ||||
|   - 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`) | ||||
|   - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) | ||||
|   - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) | ||||
|   - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) | ||||
|  | ||||
| SPEC REPOS: | ||||
|   trunk: | ||||
|     - DKImagePickerController | ||||
|     - DKPhotoGallery | ||||
|     - libwebp | ||||
|     - Mantle | ||||
|     - SDWebImage | ||||
|     - SDWebImageWebPCoder | ||||
|     - SwiftyGif | ||||
|  | ||||
| EXTERNAL SOURCES: | ||||
|   connectivity_plus: | ||||
|     :path: ".symlinks/plugins/connectivity_plus/darwin" | ||||
|   croppy: | ||||
|     :path: ".symlinks/plugins/croppy/ios" | ||||
|   cupertino_http: | ||||
|     :path: ".symlinks/plugins/cupertino_http/ios" | ||||
|   file_picker: | ||||
|     :path: ".symlinks/plugins/file_picker/ios" | ||||
|   Flutter: | ||||
|     :path: Flutter | ||||
|   flutter_image_compress_common: | ||||
|     :path: ".symlinks/plugins/flutter_image_compress_common/ios" | ||||
|   flutter_native_splash: | ||||
|     :path: ".symlinks/plugins/flutter_native_splash/ios" | ||||
|   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" | ||||
|   shared_preferences_foundation: | ||||
| @@ -54,13 +141,24 @@ EXTERNAL SOURCES: | ||||
|  | ||||
| SPEC CHECKSUMS: | ||||
|   connectivity_plus: 4c41c08fc6d7c91f63bc7aec70ffe3730b04f563 | ||||
|   croppy: b6199bc8d56bd2e03cc11609d1c47ad9875c1321 | ||||
|   cupertino_http: 1a3a0f163c1b26e7f1a293b33d476e0fde7a64ec | ||||
|   DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c | ||||
|   DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 | ||||
|   file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 | ||||
|   Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 | ||||
|   flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e | ||||
|   flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 | ||||
|   flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec | ||||
|   image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 | ||||
|   libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 | ||||
|   Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d | ||||
|   path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 | ||||
|   SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3 | ||||
|   SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 | ||||
|   shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 | ||||
|   sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d | ||||
|   SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 | ||||
|   url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe | ||||
|  | ||||
| PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 | ||||
|   | ||||
| @@ -1,56 +1,64 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||
| <plist version="1.0"> | ||||
| 	<dict> | ||||
| 		<key>CFBundleDevelopmentRegion</key> | ||||
| 		<string>$(DEVELOPMENT_LANGUAGE)</string> | ||||
| 		<key>CFBundleDisplayName</key> | ||||
| 		<string>Surface</string> | ||||
| 		<key>CFBundleExecutable</key> | ||||
| 		<string>$(EXECUTABLE_NAME)</string> | ||||
| 		<key>CFBundleIdentifier</key> | ||||
| 		<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||||
| 		<key>CFBundleInfoDictionaryVersion</key> | ||||
| 		<string>6.0</string> | ||||
| 		<key>CFBundleName</key> | ||||
| 		<string>surface</string> | ||||
| 		<key>CFBundlePackageType</key> | ||||
| 		<string>APPL</string> | ||||
| 		<key>CFBundleShortVersionString</key> | ||||
| 		<string>$(FLUTTER_BUILD_NAME)</string> | ||||
| 		<key>CFBundleSignature</key> | ||||
| 		<string>????</string> | ||||
| 		<key>CFBundleVersion</key> | ||||
| 		<string>$(FLUTTER_BUILD_NUMBER)</string> | ||||
| 		<key>LSRequiresIPhoneOS</key> | ||||
| 		<true/> | ||||
| 		<key>UILaunchStoryboardName</key> | ||||
| 		<string>LaunchScreen</string> | ||||
| 		<key>UIMainStoryboardFile</key> | ||||
| 		<string>Main</string> | ||||
| 		<key>UISupportedInterfaceOrientations</key> | ||||
| 		<array> | ||||
| 			<string>UIInterfaceOrientationPortrait</string> | ||||
| 			<string>UIInterfaceOrientationLandscapeLeft</string> | ||||
| 			<string>UIInterfaceOrientationLandscapeRight</string> | ||||
| 		</array> | ||||
| 		<key>UISupportedInterfaceOrientations~ipad</key> | ||||
| 		<array> | ||||
| 			<string>UIInterfaceOrientationPortrait</string> | ||||
| 			<string>UIInterfaceOrientationPortraitUpsideDown</string> | ||||
| 			<string>UIInterfaceOrientationLandscapeLeft</string> | ||||
| 			<string>UIInterfaceOrientationLandscapeRight</string> | ||||
| 		</array> | ||||
| 		<key>CADisableMinimumFrameDurationOnPhone</key> | ||||
| 		<true/> | ||||
| 		<key>UIApplicationSupportsIndirectInputEvents</key> | ||||
| 		<true/> | ||||
| 		<key>CFBundleLocalizations</key> | ||||
| 		<array> | ||||
| 			<string>en</string> | ||||
| 			<string>zh_CN</string> | ||||
| 		</array> | ||||
| 		<key>UIStatusBarHidden</key> | ||||
| 		<false/> | ||||
| 	</dict> | ||||
| <dict> | ||||
| 	<key>CFBundleDevelopmentRegion</key> | ||||
| 	<string>$(DEVELOPMENT_LANGUAGE)</string> | ||||
| 	<key>CFBundleDisplayName</key> | ||||
| 	<string>Surface</string> | ||||
| 	<key>CFBundleExecutable</key> | ||||
| 	<string>$(EXECUTABLE_NAME)</string> | ||||
| 	<key>CFBundleIdentifier</key> | ||||
| 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||||
| 	<key>CFBundleInfoDictionaryVersion</key> | ||||
| 	<string>6.0</string> | ||||
| 	<key>CFBundleName</key> | ||||
| 	<string>surface</string> | ||||
| 	<key>CFBundlePackageType</key> | ||||
| 	<string>APPL</string> | ||||
| 	<key>CFBundleShortVersionString</key> | ||||
| 	<string>$(FLUTTER_BUILD_NAME)</string> | ||||
| 	<key>CFBundleSignature</key> | ||||
| 	<string>????</string> | ||||
| 	<key>CFBundleVersion</key> | ||||
| 	<string>$(FLUTTER_BUILD_NUMBER)</string> | ||||
| 	<key>LSRequiresIPhoneOS</key> | ||||
| 	<true/> | ||||
| 	<key>UILaunchStoryboardName</key> | ||||
| 	<string>LaunchScreen</string> | ||||
| 	<key>UIMainStoryboardFile</key> | ||||
| 	<string>Main</string> | ||||
| 	<key>UISupportedInterfaceOrientations</key> | ||||
| 	<array> | ||||
| 		<string>UIInterfaceOrientationPortrait</string> | ||||
| 		<string>UIInterfaceOrientationLandscapeLeft</string> | ||||
| 		<string>UIInterfaceOrientationLandscapeRight</string> | ||||
| 	</array> | ||||
| 	<key>UISupportedInterfaceOrientations~ipad</key> | ||||
| 	<array> | ||||
| 		<string>UIInterfaceOrientationPortrait</string> | ||||
| 		<string>UIInterfaceOrientationPortraitUpsideDown</string> | ||||
| 		<string>UIInterfaceOrientationLandscapeLeft</string> | ||||
| 		<string>UIInterfaceOrientationLandscapeRight</string> | ||||
| 	</array> | ||||
| 	<key>CADisableMinimumFrameDurationOnPhone</key> | ||||
| 	<true/> | ||||
| 	<key>UIApplicationSupportsIndirectInputEvents</key> | ||||
| 	<true/> | ||||
| 	<key>CFBundleLocalizations</key> | ||||
| 	<array> | ||||
| 		<string>en</string> | ||||
| 		<string>zh_CN</string> | ||||
| 	</array> | ||||
| 	<key>NSPhotoLibraryUsageDescription</key> | ||||
| 	<string>Grant access to Photo Library will allow Solian upload photo or video for your post.</string> | ||||
| 	<key>NSCameraUsageDescription</key> | ||||
| 	<string>Grant access to Photo Library will allow Solian take photo or video for your post.</string> | ||||
| 	<key>NSMicrophoneUsageDescription</key> | ||||
| 	<string>Grant access to Photo Library will allow Solian record audio for your post.</string> | ||||
| 	<key>ITSAppUsesNonExemptEncryption</key> | ||||
| 	<false/> | ||||
| 	<key>UIStatusBarHidden</key> | ||||
| 	<false/> | ||||
| </dict> | ||||
| </plist> | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:croppy/croppy.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:easy_localization_loader/easy_localization_loader.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @@ -48,6 +49,7 @@ class SolianApp extends StatelessWidget { | ||||
|               locale: context.locale, | ||||
|               supportedLocales: context.supportedLocales, | ||||
|               localizationsDelegates: [ | ||||
|                 CroppyLocalizations.delegate, | ||||
|                 RelativeTimeLocalizations.delegate, | ||||
|                 ...context.localizationDelegates, | ||||
|               ], | ||||
|   | ||||
| @@ -1,8 +1,15 @@ | ||||
| import 'dart:collection'; | ||||
| import 'dart:typed_data'; | ||||
|  | ||||
| import 'package:dio/dio.dart'; | ||||
| import 'package:flutter/widgets.dart'; | ||||
| import 'package:cross_file/cross_file.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/types/attachment.dart'; | ||||
|  | ||||
| const kConcurrentUploadChunks = 5; | ||||
|  | ||||
| class SnAttachmentProvider { | ||||
|   late final SnNetworkProvider _sn; | ||||
|   final Map<String, SnAttachment> _cache = {}; | ||||
| @@ -43,4 +50,157 @@ class SnAttachmentProvider { | ||||
|     } | ||||
|     return rids.map((rid) => _cache[rid]!).toList(); | ||||
|   } | ||||
|  | ||||
|   static Map<String, String> mimetypeOverrides = { | ||||
|     'mov': 'video/quicktime', | ||||
|     'mp4': 'video/mp4' | ||||
|   }; | ||||
|  | ||||
|   Future<SnAttachment> directUploadOne( | ||||
|     Uint8List data, | ||||
|     String filename, | ||||
|     String pool, | ||||
|     Map<String, dynamic>? metadata, { | ||||
|     String? mimetype, | ||||
|     Function(double progress)? onProgress, | ||||
|   }) async { | ||||
|     final filePayload = MultipartFile.fromBytes(data, filename: filename); | ||||
|     final fileAlt = filename.contains('.') | ||||
|         ? filename.substring(0, filename.lastIndexOf('.')) | ||||
|         : filename; | ||||
|     final fileExt = | ||||
|         filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); | ||||
|  | ||||
|     String? mimetypeOverride; | ||||
|     if (mimetype != null) { | ||||
|       mimetypeOverride = mimetype; | ||||
|     } else if (mimetypeOverrides.keys.contains(fileExt)) { | ||||
|       mimetypeOverride = mimetypeOverrides[fileExt]; | ||||
|     } | ||||
|  | ||||
|     final formData = FormData.fromMap({ | ||||
|       'alt': fileAlt, | ||||
|       'file': filePayload, | ||||
|       'pool': pool, | ||||
|       'metadata': metadata, | ||||
|       if (mimetypeOverride != null) 'mimetype': mimetypeOverride, | ||||
|     }); | ||||
|     final resp = await _sn.client.post( | ||||
|       '/cgi/uc/attachments', | ||||
|       data: formData, | ||||
|       onSendProgress: (count, total) { | ||||
|         if (onProgress != null) { | ||||
|           onProgress(count / total); | ||||
|         } | ||||
|       }, | ||||
|     ); | ||||
|  | ||||
|     return SnAttachment.fromJson(resp.data); | ||||
|   } | ||||
|  | ||||
|   Future<(SnAttachment, int)> chunkedUploadInitialize( | ||||
|     int size, | ||||
|     String filename, | ||||
|     String pool, | ||||
|     Map<String, dynamic>? metadata, | ||||
|   ) async { | ||||
|     final fileAlt = filename.contains('.') | ||||
|         ? filename.substring(0, filename.lastIndexOf('.')) | ||||
|         : filename; | ||||
|     final fileExt = | ||||
|         filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); | ||||
|  | ||||
|     String? mimetypeOverride; | ||||
|     if (mimetypeOverrides.keys.contains(fileExt)) { | ||||
|       mimetypeOverride = mimetypeOverrides[fileExt]; | ||||
|     } | ||||
|  | ||||
|     final resp = await _sn.client.post('/cgi/uc/attachments/multipart', data: { | ||||
|       'alt': fileAlt, | ||||
|       'name': filename, | ||||
|       'pool': pool, | ||||
|       'metadata': metadata, | ||||
|       'size': size, | ||||
|       if (mimetypeOverride != null) 'mimetype': mimetypeOverride, | ||||
|     }); | ||||
|  | ||||
|     return ( | ||||
|       SnAttachment.fromJson(resp.data['meta']), | ||||
|       resp.data['chunk_size'] as int | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Future<SnAttachment> _chunkedUploadOnePart( | ||||
|     Uint8List data, | ||||
|     String rid, | ||||
|     String cid, { | ||||
|     Function(double progress)? onProgress, | ||||
|   }) async { | ||||
|     final resp = await _sn.client.post( | ||||
|       '/cgi/uc/attachments/multipart/$rid/$cid', | ||||
|       data: data, | ||||
|       options: Options(headers: {'Content-Type': 'application/octet-stream'}), | ||||
|       onSendProgress: (count, total) { | ||||
|         if (onProgress != null) { | ||||
|           onProgress(count / total); | ||||
|         } | ||||
|       }, | ||||
|     ); | ||||
|  | ||||
|     return SnAttachment.fromJson(resp.data); | ||||
|   } | ||||
|  | ||||
|   Future<SnAttachment> chunkedUploadParts( | ||||
|     XFile file, | ||||
|     SnAttachment place, | ||||
|     int chunkSize, { | ||||
|     Function(double progress)? onProgress, | ||||
|   }) async { | ||||
|     final Map<String, dynamic> chunks = place.fileChunks ?? {}; | ||||
|     var currentTask = 0; | ||||
|  | ||||
|     final queue = Queue<Future<void>>(); | ||||
|     final activeTasks = <Future<void>>[]; | ||||
|  | ||||
|     for (final entry in chunks.entries) { | ||||
|       queue.add(() async { | ||||
|         final beginCursor = entry.value * chunkSize; | ||||
|         final endCursor = (entry.value + 1) * chunkSize; | ||||
|         final data = Uint8List.fromList(await file | ||||
|             .openRead(beginCursor, endCursor) | ||||
|             .expand((chunk) => chunk) | ||||
|             .toList()); | ||||
|  | ||||
|         place = await _chunkedUploadOnePart( | ||||
|           data, | ||||
|           place.rid, | ||||
|           entry.key, | ||||
|           onProgress: (chunkProgress) { | ||||
|             final overallProgress = | ||||
|                 (currentTask + chunkProgress) / chunks.length; | ||||
|             if (onProgress != null) { | ||||
|               onProgress(overallProgress); | ||||
|             } | ||||
|           }, | ||||
|         ); | ||||
|  | ||||
|         currentTask++; | ||||
|       }()); | ||||
|     } | ||||
|  | ||||
|     while (queue.isNotEmpty || activeTasks.isNotEmpty) { | ||||
|       while (activeTasks.length < kConcurrentUploadChunks && queue.isNotEmpty) { | ||||
|         final task = queue.removeFirst(); | ||||
|         activeTasks.add(task); | ||||
|  | ||||
|         task.then((_) => activeTasks.remove(task)); | ||||
|       } | ||||
|  | ||||
|       if (activeTasks.isNotEmpty) { | ||||
|         await Future.any(activeTasks); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return place; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,9 @@ | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:surface/screens/account.dart'; | ||||
| import 'package:surface/screens/account/publisher_edit.dart'; | ||||
| import 'package:surface/screens/account/publisher_new.dart'; | ||||
| import 'package:surface/screens/account/publishers.dart'; | ||||
| import 'package:surface/screens/account/profile_edit.dart'; | ||||
| import 'package:surface/screens/account/publishers/publisher_edit.dart'; | ||||
| import 'package:surface/screens/account/publishers/publisher_new.dart'; | ||||
| import 'package:surface/screens/account/publishers/publishers.dart'; | ||||
| import 'package:surface/screens/auth/login.dart'; | ||||
| import 'package:surface/screens/auth/register.dart'; | ||||
| import 'package:surface/screens/explore.dart'; | ||||
| @@ -50,6 +51,11 @@ final appRouter = GoRouter( | ||||
|           name: 'authRegister', | ||||
|           builder: (context, state) => const RegisterScreen(), | ||||
|         ), | ||||
|         GoRoute( | ||||
|           path: '/account/profile/edit', | ||||
|           name: 'accountProfileEdit', | ||||
|           builder: (context, state) => const ProfileEditScreen(), | ||||
|         ), | ||||
|         GoRoute( | ||||
|           path: '/account/publishers', | ||||
|           name: 'accountPublishers', | ||||
|   | ||||
| @@ -74,6 +74,16 @@ class _AuthorizedAccountScreen extends StatelessWidget { | ||||
|             ); | ||||
|           }).padding(all: 20), | ||||
|         ).padding(horizontal: 8, top: 16, bottom: 4), | ||||
|         ListTile( | ||||
|           title: Text('accountProfileEdit').tr(), | ||||
|           subtitle: Text('accountProfileEditSubtitle').tr(), | ||||
|           contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||
|           leading: const Icon(Symbols.contact_page), | ||||
|           trailing: const Icon(Icons.chevron_right), | ||||
|           onTap: () { | ||||
|             GoRouter.of(context).pushNamed('accountProfileEdit'); | ||||
|           }, | ||||
|         ), | ||||
|         ListTile( | ||||
|           title: Text('accountPublishers').tr(), | ||||
|           subtitle: Text('accountPublishersSubtitle').tr(), | ||||
|   | ||||
							
								
								
									
										348
									
								
								lib/screens/account/profile_edit.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										348
									
								
								lib/screens/account/profile_edit.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,348 @@ | ||||
| import 'dart:io'; | ||||
| import 'dart:ui'; | ||||
|  | ||||
| import 'package:croppy/croppy.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:image_picker/image_picker.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:path/path.dart' show basename; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:surface/providers/sn_attachment.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/providers/userinfo.dart'; | ||||
| import 'package:surface/widgets/account/account_image.dart'; | ||||
| import 'package:surface/widgets/dialog.dart'; | ||||
| import 'package:surface/widgets/loading_indicator.dart'; | ||||
| import 'package:surface/widgets/universal_image.dart'; | ||||
|  | ||||
| class ProfileEditScreen extends StatefulWidget { | ||||
|   const ProfileEditScreen({super.key}); | ||||
|  | ||||
|   @override | ||||
|   State<ProfileEditScreen> createState() => _ProfileEditScreenState(); | ||||
| } | ||||
|  | ||||
| class _ProfileEditScreenState extends State<ProfileEditScreen> { | ||||
|   final _imagePicker = ImagePicker(); | ||||
|  | ||||
|   final _usernameController = TextEditingController(); | ||||
|   final _nicknameController = TextEditingController(); | ||||
|   final _firstNameController = TextEditingController(); | ||||
|   final _lastNameController = TextEditingController(); | ||||
|   final _descriptionController = TextEditingController(); | ||||
|   final _birthdayController = TextEditingController(); | ||||
|  | ||||
|   String? _avatar; | ||||
|   String? _banner; | ||||
|   DateTime? _birthday; | ||||
|  | ||||
|   bool _isBusy = false; | ||||
|  | ||||
|   static const _kDateFormat = 'y/M/d'; | ||||
|  | ||||
|   void _syncWidget() async { | ||||
|     final ua = context.read<UserProvider>(); | ||||
|     final prof = ua.user!; | ||||
|     _usernameController.text = prof.name; | ||||
|     _nicknameController.text = prof.nick; | ||||
|     _descriptionController.text = prof.description; | ||||
|     _firstNameController.text = prof.profile!.firstName; | ||||
|     _lastNameController.text = prof.profile!.lastName; | ||||
|     _avatar = prof.avatar; | ||||
|     _banner = prof.banner; | ||||
|     if (prof.profile!.birthday != null) { | ||||
|       _birthdayController.text = DateFormat(_kDateFormat).format( | ||||
|         prof.profile!.birthday!.toLocal(), | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void _selectBirthday() async { | ||||
|     await showCupertinoModalPopup<DateTime?>( | ||||
|       context: context, | ||||
|       builder: (BuildContext context) => Container( | ||||
|         height: 216, | ||||
|         padding: const EdgeInsets.only(top: 6.0), | ||||
|         margin: EdgeInsets.only( | ||||
|           bottom: MediaQuery.of(context).viewInsets.bottom, | ||||
|         ), | ||||
|         color: Theme.of(context).colorScheme.surface, | ||||
|         child: SafeArea( | ||||
|           top: false, | ||||
|           child: CupertinoDatePicker( | ||||
|             initialDateTime: _birthday?.toLocal(), | ||||
|             mode: CupertinoDatePickerMode.date, | ||||
|             use24hFormat: true, | ||||
|             onDateTimeChanged: (DateTime newDate) { | ||||
|               setState(() { | ||||
|                 _birthday = newDate; | ||||
|                 _birthdayController.text = | ||||
|                     DateFormat(_kDateFormat).format(_birthday!); | ||||
|               }); | ||||
|             }, | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Future<void> _updateImage(String place) async { | ||||
|     final image = await _imagePicker.pickImage(source: ImageSource.gallery); | ||||
|     if (image == null) return; | ||||
|     if (!mounted) return; | ||||
|  | ||||
|     final ImageProvider imageProvider = | ||||
|         kIsWeb ? NetworkImage(image.path) : FileImage(File(image.path)); | ||||
|     final aspectRatios = place == 'banner' | ||||
|         ? [CropAspectRatio(width: 16, height: 7)] | ||||
|         : [CropAspectRatio(width: 1, height: 1)]; | ||||
|     final result = (!kIsWeb && (Platform.isIOS || Platform.isMacOS)) | ||||
|         ? await showCupertinoImageCropper( | ||||
|             // ignore: use_build_context_synchronously | ||||
|             context, | ||||
|             allowedAspectRatios: aspectRatios, | ||||
|             imageProvider: imageProvider, | ||||
|           ) | ||||
|         : await showMaterialImageCropper( | ||||
|             // ignore: use_build_context_synchronously | ||||
|             context, | ||||
|             allowedAspectRatios: aspectRatios, | ||||
|             imageProvider: imageProvider, | ||||
|           ); | ||||
|  | ||||
|     if (result == null) return; | ||||
|  | ||||
|     if (!mounted) return; | ||||
|     final attach = context.read<SnAttachmentProvider>(); | ||||
|  | ||||
|     setState(() => _isBusy = true); | ||||
|  | ||||
|     final rawBytes = | ||||
|         (await result.uiImage.toByteData(format: ImageByteFormat.png))! | ||||
|             .buffer | ||||
|             .asUint8List(); | ||||
|  | ||||
|     try { | ||||
|       final attachment = await attach.directUploadOne( | ||||
|         rawBytes, | ||||
|         basename(image.path), | ||||
|         'avatar', | ||||
|         null, | ||||
|         mimetype: 'image/png', | ||||
|       ); | ||||
|  | ||||
|       if (!mounted) return; | ||||
|       final sn = context.read<SnNetworkProvider>(); | ||||
|       await sn.client.put( | ||||
|         '/cgi/id/users/me/$place', | ||||
|         data: {'attachment': attachment.rid}, | ||||
|       ); | ||||
|  | ||||
|       if (!mounted) return; | ||||
|       final ua = context.read<UserProvider>(); | ||||
|       await ua.refreshUser(); | ||||
|  | ||||
|       if (!mounted) return; | ||||
|       context.showSnackbar('accountProfileEditApplied'.tr()); | ||||
|       _syncWidget(); | ||||
|     } catch (err) { | ||||
|       if (!mounted) return; | ||||
|       context.showErrorDialog(err); | ||||
|     } finally { | ||||
|       setState(() => _isBusy = false); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void _updateUserInfo() async { | ||||
|     setState(() => _isBusy = true); | ||||
|  | ||||
|     final sn = context.read<SnNetworkProvider>(); | ||||
|  | ||||
|     try { | ||||
|       await sn.client.put( | ||||
|         '/cgi/id/users/me', | ||||
|         data: { | ||||
|           'nick': _nicknameController.value.text, | ||||
|           'description': _descriptionController.value.text, | ||||
|           'first_name': _firstNameController.value.text, | ||||
|           'last_name': _lastNameController.value.text, | ||||
|           'birthday': _birthday?.toUtc().toIso8601String(), | ||||
|         }, | ||||
|       ); | ||||
|  | ||||
|       if (!mounted) return; | ||||
|       final ua = context.read<UserProvider>(); | ||||
|       await ua.refreshUser(); | ||||
|  | ||||
|       if (!mounted) return; | ||||
|       context.showSnackbar('accountProfileEditApplied'.tr()); | ||||
|       _syncWidget(); | ||||
|     } catch (err) { | ||||
|       context.showErrorDialog(err); | ||||
|     } finally { | ||||
|       setState(() => _isBusy = false); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _syncWidget(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void dispose() { | ||||
|     _usernameController.dispose(); | ||||
|     _nicknameController.dispose(); | ||||
|     _firstNameController.dispose(); | ||||
|     _lastNameController.dispose(); | ||||
|     _descriptionController.dispose(); | ||||
|     _birthdayController.dispose(); | ||||
|     super.dispose(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     const double padding = 24; | ||||
|  | ||||
|     final sn = context.read<SnNetworkProvider>(); | ||||
|  | ||||
|     return SingleChildScrollView( | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           LoadingIndicator(isActive: _isBusy), | ||||
|           const Gap(24), | ||||
|           Stack( | ||||
|             clipBehavior: Clip.none, | ||||
|             children: [ | ||||
|               Material( | ||||
|                 elevation: 0, | ||||
|                 child: InkWell( | ||||
|                   child: ClipRRect( | ||||
|                     borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|                     child: AspectRatio( | ||||
|                       aspectRatio: 16 / 9, | ||||
|                       child: Container( | ||||
|                         color: | ||||
|                             Theme.of(context).colorScheme.surfaceContainerHigh, | ||||
|                         child: _banner != null | ||||
|                             ? UniversalImage( | ||||
|                                 sn.getAttachmentUrl(_banner!), | ||||
|                                 fit: BoxFit.cover, | ||||
|                               ) | ||||
|                             : const SizedBox.shrink(), | ||||
|                       ), | ||||
|                     ), | ||||
|                   ), | ||||
|                   onTap: () { | ||||
|                     _updateImage('banner'); | ||||
|                   }, | ||||
|                 ), | ||||
|               ), | ||||
|               Positioned( | ||||
|                 bottom: -28, | ||||
|                 left: 16, | ||||
|                 child: Material( | ||||
|                   elevation: 2, | ||||
|                   borderRadius: const BorderRadius.all(Radius.circular(40)), | ||||
|                   child: InkWell( | ||||
|                     child: AccountImage(content: _avatar, radius: 40), | ||||
|                     onTap: () { | ||||
|                       _updateImage('avatar'); | ||||
|                     }, | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             ], | ||||
|           ).padding(horizontal: padding), | ||||
|           const Gap(8 + 28), | ||||
|           Column( | ||||
|             children: [ | ||||
|               TextField( | ||||
|                 readOnly: true, | ||||
|                 controller: _usernameController, | ||||
|                 decoration: InputDecoration( | ||||
|                   border: const UnderlineInputBorder(), | ||||
|                   labelText: 'fieldUsername'.tr(), | ||||
|                   helperText: 'fieldUsernameCannotEditHint'.tr(), | ||||
|                 ), | ||||
|               ), | ||||
|               const Gap(4), | ||||
|               TextField( | ||||
|                 controller: _nicknameController, | ||||
|                 decoration: InputDecoration( | ||||
|                   border: const UnderlineInputBorder(), | ||||
|                   labelText: 'fieldNickname'.tr(), | ||||
|                 ), | ||||
|               ), | ||||
|               const Gap(4), | ||||
|               Row( | ||||
|                 children: [ | ||||
|                   Flexible( | ||||
|                     flex: 1, | ||||
|                     child: TextField( | ||||
|                       controller: _firstNameController, | ||||
|                       decoration: InputDecoration( | ||||
|                         border: const UnderlineInputBorder(), | ||||
|                         labelText: 'fieldFirstName'.tr(), | ||||
|                       ), | ||||
|                     ), | ||||
|                   ), | ||||
|                   const Gap(8), | ||||
|                   Flexible( | ||||
|                     flex: 1, | ||||
|                     child: TextField( | ||||
|                       controller: _lastNameController, | ||||
|                       decoration: InputDecoration( | ||||
|                         border: const UnderlineInputBorder(), | ||||
|                         labelText: 'fieldLastName'.tr(), | ||||
|                       ), | ||||
|                     ), | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|               const Gap(4), | ||||
|               TextField( | ||||
|                 controller: _descriptionController, | ||||
|                 keyboardType: TextInputType.multiline, | ||||
|                 maxLines: null, | ||||
|                 minLines: 3, | ||||
|                 decoration: InputDecoration( | ||||
|                   border: const UnderlineInputBorder(), | ||||
|                   labelText: 'fieldDescription'.tr(), | ||||
|                 ), | ||||
|               ), | ||||
|               const Gap(4), | ||||
|               TextField( | ||||
|                 controller: _birthdayController, | ||||
|                 readOnly: true, | ||||
|                 decoration: InputDecoration( | ||||
|                   border: const UnderlineInputBorder(), | ||||
|                   labelText: 'fieldBirthday'.tr(), | ||||
|                 ), | ||||
|                 onTap: () => _selectBirthday(), | ||||
|               ), | ||||
|             ], | ||||
|           ).padding(horizontal: padding + 8), | ||||
|           const Gap(12), | ||||
|           Row( | ||||
|             mainAxisAlignment: MainAxisAlignment.end, | ||||
|             children: [ | ||||
|               ElevatedButton.icon( | ||||
|                 onPressed: _isBusy ? null : _updateUserInfo, | ||||
|                 icon: const Icon(Symbols.save), | ||||
|                 label: Text('apply').tr(), | ||||
|               ), | ||||
|             ], | ||||
|           ).padding(horizontal: padding), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -134,7 +134,7 @@ class _AccountPublisherEditScreenState | ||||
|             const Gap(4), | ||||
|             TextField( | ||||
|               controller: _descriptionController, | ||||
|               maxLines: 3, | ||||
|               maxLines: null, | ||||
|               minLines: 3, | ||||
|               decoration: InputDecoration( | ||||
|                 labelText: 'fieldDescription'.tr(), | ||||
| @@ -4,7 +4,7 @@ import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/widgets/universal_image.dart'; | ||||
|  | ||||
| class AccountImage extends StatelessWidget { | ||||
|   final String content; | ||||
|   final String? content; | ||||
|   final Color? backgroundColor; | ||||
|   final Color? foregroundColor; | ||||
|   final double? radius; | ||||
| @@ -22,21 +22,21 @@ class AccountImage extends StatelessWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final sn = context.read<SnNetworkProvider>(); | ||||
|     final url = sn.getAttachmentUrl(content); | ||||
|     final url = sn.getAttachmentUrl(content ?? ''); | ||||
|  | ||||
|     final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; | ||||
|     return CircleAvatar( | ||||
|       key: Key('attachment-${content.hashCode}'), | ||||
|       radius: radius, | ||||
|       backgroundColor: backgroundColor, | ||||
|       backgroundImage: content.isNotEmpty | ||||
|       backgroundImage: (content?.isNotEmpty ?? false) | ||||
|           ? ResizeImage( | ||||
|               UniversalImage.provider(url), | ||||
|               width: ((radius ?? 20) * devicePixelRatio * 2).round(), | ||||
|               height: ((radius ?? 20) * devicePixelRatio * 2).round(), | ||||
|             ) | ||||
|           : null, | ||||
|       child: content.isEmpty | ||||
|       child: (content?.isEmpty ?? true) | ||||
|           ? (fallbackWidget ?? | ||||
|               Icon( | ||||
|                 Icons.account_circle, | ||||
|   | ||||
| @@ -6,10 +6,14 @@ | ||||
|  | ||||
| #include "generated_plugin_registrant.h" | ||||
|  | ||||
| #include <file_selector_linux/file_selector_plugin.h> | ||||
| #include <flutter_secure_storage/flutter_secure_storage_plugin.h> | ||||
| #include <url_launcher_linux/url_launcher_plugin.h> | ||||
|  | ||||
| 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_registrar = | ||||
|       fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStoragePlugin"); | ||||
|   flutter_secure_storage_plugin_register_with_registrar(flutter_secure_storage_registrar); | ||||
|   | ||||
| @@ -3,11 +3,13 @@ | ||||
| # | ||||
|  | ||||
| list(APPEND FLUTTER_PLUGIN_LIST | ||||
|   file_selector_linux | ||||
|   flutter_secure_storage | ||||
|   url_launcher_linux | ||||
| ) | ||||
|  | ||||
| list(APPEND FLUTTER_FFI_PLUGIN_LIST | ||||
|   croppy | ||||
|   jni | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -6,6 +6,8 @@ import FlutterMacOS | ||||
| import Foundation | ||||
|  | ||||
| import connectivity_plus | ||||
| import file_selector_macos | ||||
| import flutter_image_compress_macos | ||||
| import path_provider_foundation | ||||
| import shared_preferences_foundation | ||||
| import sqflite_darwin | ||||
| @@ -13,6 +15,8 @@ import url_launcher_macos | ||||
|  | ||||
| func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { | ||||
|   ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) | ||||
|   FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) | ||||
|   FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin")) | ||||
|   PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) | ||||
|   SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) | ||||
|   SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) | ||||
|   | ||||
							
								
								
									
										200
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										200
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -158,6 +158,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.3.1" | ||||
|   cassowary: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: cassowary | ||||
|       sha256: f304452beaf93b9349daaeeda23f853578c9dd8674c06c6100fda0319c46b967 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.4.3" | ||||
|   characters: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -230,6 +238,22 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.3.2" | ||||
|   croppy: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: croppy | ||||
|       sha256: "14bb40fd6c1771b093a907ddbf24df9aa49a4e6e379dd630602eb446e30ec629" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.3.1" | ||||
|   cross_file: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: cross_file | ||||
|       sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.3.4+2" | ||||
|   crypto: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -334,6 +358,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.0.2" | ||||
|   equatable: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: equatable | ||||
|       sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.0.5" | ||||
|   fake_async: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -358,6 +390,46 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "7.0.1" | ||||
|   file_picker: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: file_picker | ||||
|       sha256: aac85f20436608e01a6ffd1fdd4e746a7f33c93a2c83752e626bdfaea139b877 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "8.1.3" | ||||
|   file_selector_linux: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: file_selector_linux | ||||
|       sha256: "712ce7fab537ba532c8febdb1a8f167b32441e74acd68c3ccb2e36dcb52c4ab2" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.9.3" | ||||
|   file_selector_macos: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: file_selector_macos | ||||
|       sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.9.4+2" | ||||
|   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: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.9.3+3" | ||||
|   fixnum: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -387,6 +459,54 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.4.1" | ||||
|   flutter_image_compress: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: flutter_image_compress | ||||
|       sha256: "45a3071868092a61b11044c70422b04d39d4d9f2ef536f3c5b11fb65a1e7dd90" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.3.0" | ||||
|   flutter_image_compress_common: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_image_compress_common | ||||
|       sha256: "7f79bc6c8a363063620b4e372fa86bc691e1cb28e58048cd38e030692fbd99ee" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.0.5" | ||||
|   flutter_image_compress_macos: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_image_compress_macos | ||||
|       sha256: "26df6385512e92b3789dc76b613b54b55c457a7f1532e59078b04bf189782d47" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.0.2" | ||||
|   flutter_image_compress_ohos: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_image_compress_ohos | ||||
|       sha256: e76b92bbc830ee08f5b05962fc78a532011fcd2041f620b5400a593e96da3f51 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.0.3" | ||||
|   flutter_image_compress_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_image_compress_platform_interface | ||||
|       sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.0.5" | ||||
|   flutter_image_compress_web: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_image_compress_web | ||||
|       sha256: f02fe352b17f82b72f481de45add240db062a2585850bea1667e82cc4cd6c311 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.1.4+1" | ||||
|   flutter_lints: | ||||
|     dependency: "direct dev" | ||||
|     description: | ||||
| @@ -416,6 +536,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.4.2" | ||||
|   flutter_plugin_android_lifecycle: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_plugin_android_lifecycle | ||||
|       sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.0.23" | ||||
|   flutter_secure_storage: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -562,6 +690,70 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "4.3.0" | ||||
|   image_picker: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: image_picker | ||||
|       sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.1.2" | ||||
|   image_picker_android: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: image_picker_android | ||||
|       sha256: "8faba09ba361d4b246dc0a17cb4289b3324c2b9f6db7b3d457ee69106a86bd32" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.8.12+17" | ||||
|   image_picker_for_web: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: image_picker_for_web | ||||
|       sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.0.6" | ||||
|   image_picker_ios: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: image_picker_ios | ||||
|       sha256: "4f0568120c6fcc0aaa04511cb9f9f4d29fc3d0139884b1d06be88dcec7641d6b" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.8.12+1" | ||||
|   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" | ||||
|   intl: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1263,6 +1455,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.0.1" | ||||
|   win32: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: win32 | ||||
|       sha256: "84ba388638ed7a8cb3445a320c8273136ab2631cd5f2c57888335504ddab1bc2" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "5.8.0" | ||||
|   xdg_directories: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
| @@ -59,6 +59,11 @@ dependencies: | ||||
|   path: ^1.9.0 | ||||
|   relative_time: ^5.0.0 | ||||
|   flutter_secure_storage: ^4.2.1 | ||||
|   image_picker: ^1.1.2 | ||||
|   cross_file: ^0.3.4+2 | ||||
|   file_picker: ^8.1.3 | ||||
|   flutter_image_compress: ^2.3.0 | ||||
|   croppy: ^1.3.1 | ||||
|  | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|   | ||||
| @@ -7,11 +7,14 @@ | ||||
| #include "generated_plugin_registrant.h" | ||||
|  | ||||
| #include <connectivity_plus/connectivity_plus_windows_plugin.h> | ||||
| #include <file_selector_windows/file_selector_windows.h> | ||||
| #include <url_launcher_windows/url_launcher_windows.h> | ||||
|  | ||||
| void RegisterPlugins(flutter::PluginRegistry* registry) { | ||||
|   ConnectivityPlusWindowsPluginRegisterWithRegistrar( | ||||
|       registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); | ||||
|   FileSelectorWindowsRegisterWithRegistrar( | ||||
|       registry->GetRegistrarForPlugin("FileSelectorWindows")); | ||||
|   UrlLauncherWindowsRegisterWithRegistrar( | ||||
|       registry->GetRegistrarForPlugin("UrlLauncherWindows")); | ||||
| } | ||||
|   | ||||
| @@ -4,10 +4,12 @@ | ||||
|  | ||||
| list(APPEND FLUTTER_PLUGIN_LIST | ||||
|   connectivity_plus | ||||
|   file_selector_windows | ||||
|   url_launcher_windows | ||||
| ) | ||||
|  | ||||
| list(APPEND FLUTTER_FFI_PLUGIN_LIST | ||||
|   croppy | ||||
|   jni | ||||
| ) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user