diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index d87d758..18954dc 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -41,5 +41,13 @@ UIViewControllerBasedStatusBarAppearance + + + NSPhotoLibraryUsageDescription + For uploading images for posts/avatars + NSCameraUsageDescription + For uploading images for posts/avatars + NSMicrophoneUsageDescription + For recording videos for posts diff --git a/lib/hooks/image_picker.dart b/lib/hooks/image_picker.dart new file mode 100644 index 0000000..8f2babd --- /dev/null +++ b/lib/hooks/image_picker.dart @@ -0,0 +1,4 @@ +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:image_picker/image_picker.dart'; + +ImagePicker useImagePicker() => useMemoized(() => ImagePicker()); diff --git a/lib/pages/create_post.dart b/lib/pages/create_post.dart index 7535aa8..c2c6d4b 100644 --- a/lib/pages/create_post.dart +++ b/lib/pages/create_post.dart @@ -1,14 +1,17 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:lemmy_api_client/lemmy_api_client.dart'; import '../hooks/delayed_loading.dart'; +import '../hooks/image_picker.dart'; import '../hooks/logged_in_action.dart'; import '../hooks/memo_future.dart'; import '../hooks/stores.dart'; import '../util/extensions/api.dart'; import '../util/goto.dart'; +import '../util/pictrs.dart'; import '../util/spaced.dart'; import '../widgets/markdown_text.dart'; import 'full_post.dart'; @@ -48,6 +51,9 @@ class CreatePost extends HookWidget { final showFancy = useState(false); final nsfw = useState(false); final delayed = useDelayedLoading(); + final imagePicker = useImagePicker(); + final imageUploadLoading = useState(false); + final pictrsDeleteToken = useState(null); final allCommunitiesSnap = useMemoFuture( () => LemmyApi(selectedInstance.value) @@ -66,6 +72,39 @@ class CreatePost extends HookWidget { [selectedInstance.value], ); + uploadPicture() async { + try { + final pic = await imagePicker.getImage(source: ImageSource.gallery); + // pic is null when the picker was cancelled + if (pic != null) { + imageUploadLoading.value = true; + + final pictrs = LemmyApi(selectedInstance.value).pictrs; + final upload = await pictrs.upload(pic.path); + + pictrsDeleteToken.value = upload.files[0]; + urlController.text = + pathToPictrs(selectedInstance.value, upload.files[0].file); + } + // ignore: avoid_catches_without_on_clauses + } catch (e) { + scaffoldKey.currentState + .showSnackBar(SnackBar(content: Text('Failed to upload image'))); + } finally { + imageUploadLoading.value = false; + } + } + + removePicture() { + LemmyApi(selectedInstance.value) + .pictrs + .delete(pictrsDeleteToken.value) + .catchError((_) {}); + + pictrsDeleteToken.value = null; + urlController.text = ''; + } + // TODO: use drop down from AddAccountPage final instanceDropdown = InputDecorator( decoration: const InputDecoration( @@ -113,13 +152,30 @@ class CreatePost extends HookWidget { ), ); - final url = TextField( - controller: urlController, - decoration: InputDecoration( - border: OutlineInputBorder(), - labelText: 'URL', - suffixIcon: Icon(Icons.link)), - ); + final url = Row(children: [ + Expanded( + child: TextField( + enabled: pictrsDeleteToken.value == null, + controller: urlController, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: 'URL', + suffixIcon: Icon(Icons.link)), + ), + ), + SizedBox(width: 5), + IconButton( + icon: imageUploadLoading.value + ? CircularProgressIndicator() + : Icon(pictrsDeleteToken.value == null + ? Icons.add_photo_alternate + : Icons.close), + onPressed: + pictrsDeleteToken.value == null ? uploadPicture : removePicture, + tooltip: + pictrsDeleteToken.value == null ? 'Add picture' : 'Delete picture', + ) + ]); final title = TextField( controller: titleController, diff --git a/lib/util/pictrs.dart b/lib/util/pictrs.dart new file mode 100644 index 0000000..e0b7d55 --- /dev/null +++ b/lib/util/pictrs.dart @@ -0,0 +1,2 @@ +String pathToPictrs(String instanceUrl, String imgId) => + 'https://$instanceUrl/pictrs/image/$imgId'; diff --git a/pubspec.lock b/pubspec.lock index 621e2d1..dd26301 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -265,6 +265,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0+2" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.11" flutter_slidable: dependency: "direct main" description: @@ -338,6 +345,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.4" + image_picker: + dependency: "direct main" + description: + name: image_picker + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.7+12" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" intl: dependency: transitive description: @@ -379,7 +400,7 @@ packages: name: lemmy_api_client url: "https://pub.dartlang.org" source: hosted - version: "0.6.0" + version: "0.7.3" logging: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2900aac..b087627 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,7 @@ dependencies: url_launcher: ^5.5.1 shared_preferences: ">=0.5.0 <2.0.0" package_info: ^0.4.3 + image_picker: ^0.6.7 # state management flutter_hooks: ^0.13.2 @@ -44,7 +45,7 @@ dependencies: # utils timeago: ^2.0.27 fuzzy: <1.0.0 - lemmy_api_client: ^0.6.0 + lemmy_api_client: ^0.7.3 flutter: sdk: flutter