diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9efad93..bb26450 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,6 +1,4 @@ PODS: - - audio_session (0.0.1): - - Flutter - connectivity_plus (0.0.1): - Flutter - FlutterMacOS @@ -223,11 +221,15 @@ PODS: - TOCropViewController (~> 2.7.4) - image_picker_ios (0.0.1): - Flutter - - just_audio (0.0.1): - - Flutter - livekit_client (2.2.4): - Flutter - WebRTC-SDK (= 125.6422.04) + - media_kit_libs_ios_video (1.0.4): + - Flutter + - media_kit_native_event_loop (1.0.0): + - Flutter + - media_kit_video (0.0.1): + - Flutter - nanopb (3.30910.0): - nanopb/decode (= 3.30910.0) - nanopb/encode (= 3.30910.0) @@ -249,6 +251,8 @@ PODS: - PromisesObjC (= 2.4.0) - protocol_handler_ios (0.0.1): - Flutter + - screen_brightness_ios (0.1.0): + - Flutter - SDWebImage (5.19.7): - SDWebImage/Core (= 5.19.7) - SDWebImage/Core (5.19.7) @@ -264,15 +268,13 @@ PODS: - TOCropViewController (2.7.4) - url_launcher_ios (0.0.1): - Flutter - - video_player_avfoundation (0.0.1): + - volume_controller (0.0.1): - Flutter - - FlutterMacOS - wakelock_plus (0.0.1): - Flutter - WebRTC-SDK (125.6422.04) DEPENDENCIES: - - audio_session (from `.symlinks/plugins/audio_session/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) @@ -289,19 +291,22 @@ DEPENDENCIES: - gal (from `.symlinks/plugins/gal/darwin`) - image_cropper (from `.symlinks/plugins/image_cropper/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - - just_audio (from `.symlinks/plugins/just_audio/ios`) - livekit_client (from `.symlinks/plugins/livekit_client/ios`) + - media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`) + - media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`) + - media_kit_video (from `.symlinks/plugins/media_kit_video/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - pasteboard (from `.symlinks/plugins/pasteboard/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - pointer_interceptor_ios (from `.symlinks/plugins/pointer_interceptor_ios/ios`) - protocol_handler_ios (from `.symlinks/plugins/protocol_handler_ios/ios`) + - screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite (from `.symlinks/plugins/sqflite/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) + - volume_controller (from `.symlinks/plugins/volume_controller/ios`) - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) SPEC REPOS: @@ -334,8 +339,6 @@ SPEC REPOS: - WebRTC-SDK EXTERNAL SOURCES: - audio_session: - :path: ".symlinks/plugins/audio_session/ios" connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/darwin" device_info_plus: @@ -368,10 +371,14 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/image_cropper/ios" image_picker_ios: :path: ".symlinks/plugins/image_picker_ios/ios" - just_audio: - :path: ".symlinks/plugins/just_audio/ios" livekit_client: :path: ".symlinks/plugins/livekit_client/ios" + media_kit_libs_ios_video: + :path: ".symlinks/plugins/media_kit_libs_ios_video/ios" + media_kit_native_event_loop: + :path: ".symlinks/plugins/media_kit_native_event_loop/ios" + media_kit_video: + :path: ".symlinks/plugins/media_kit_video/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" pasteboard: @@ -384,6 +391,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/pointer_interceptor_ios/ios" protocol_handler_ios: :path: ".symlinks/plugins/protocol_handler_ios/ios" + screen_brightness_ios: + :path: ".symlinks/plugins/screen_brightness_ios/ios" share_plus: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: @@ -392,13 +401,12 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/sqflite/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" - video_player_avfoundation: - :path: ".symlinks/plugins/video_player_avfoundation/darwin" + volume_controller: + :path: ".symlinks/plugins/volume_controller/ios" wakelock_plus: :path: ".symlinks/plugins/wakelock_plus/ios" SPEC CHECKSUMS: - audio_session: 088d2483ebd1dc43f51d253d4a1c517d9a2e7207 connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c @@ -434,8 +442,10 @@ SPEC CHECKSUMS: GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d image_cropper: 37d40f62177c101ff4c164906d259ea2c3aa70cf image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 - just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa livekit_client: d079c5f040d4bf2b80440ff0ae997725a183e4bc + media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1 + media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a + media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c pasteboard: 982969ebaa7c78af3e6cc7761e8f5e77565d9ce0 @@ -445,6 +455,7 @@ SPEC CHECKSUMS: PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 protocol_handler_ios: a5db8abc38526ee326988b808be621e5fd568990 + screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3 share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 @@ -452,7 +463,7 @@ SPEC CHECKSUMS: SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 + volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9 wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 WebRTC-SDK: c3d69a87e7185fad3568f6f3cff7c9ac5890acf3 diff --git a/lib/providers/content/attachment.dart b/lib/providers/content/attachment.dart index 765a502..7ed5e30 100644 --- a/lib/providers/content/attachment.dart +++ b/lib/providers/content/attachment.dart @@ -192,6 +192,7 @@ class AttachmentProvider extends GetConnect { Future updateAttachment( int id, String alt, { + required Map metadata, bool isMature = false, }) async { final AuthProvider auth = Get.find(); @@ -201,6 +202,7 @@ class AttachmentProvider extends GetConnect { var resp = await client.put('/attachments/$id', { 'alt': alt, + 'metadata': metadata, 'is_mature': isMature, }); diff --git a/lib/widgets/attachments/attachment_attr_editor.dart b/lib/widgets/attachments/attachment_attr_editor.dart index d799630..f8ad6be 100644 --- a/lib/widgets/attachments/attachment_attr_editor.dart +++ b/lib/widgets/attachments/attachment_attr_editor.dart @@ -37,6 +37,7 @@ class _AttachmentAttrEditorDialogState widget.item.id, _altController.value.text, isMature: _isMature, + metadata: widget.item.metadata ?? {}, ); Get.find().clearCache(id: widget.item.rid); diff --git a/lib/widgets/attachments/attachment_editor.dart b/lib/widgets/attachments/attachment_editor.dart index 32b350a..a297455 100644 --- a/lib/widgets/attachments/attachment_editor.dart +++ b/lib/widgets/attachments/attachment_editor.dart @@ -20,6 +20,7 @@ import 'package:solian/providers/attachment_uploader.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/providers/content/attachment.dart'; import 'package:solian/widgets/attachments/attachment_attr_editor.dart'; +import 'package:solian/widgets/attachments/attachment_editor_thumbnail.dart'; import 'package:solian/widgets/attachments/attachment_fullscreen.dart'; class AttachmentEditorPopup extends StatefulWidget { @@ -264,6 +265,21 @@ class _AttachmentEditorPopupState extends State { ); } + void _showAttachmentThumbnailEditor(Attachment element, int idx) { + showDialog( + context: context, + builder: (context) => AttachmentEditorThumbnailDialog( + item: element, + pool: widget.pool, + initialItem: element.metadata?['thumbnail'], + onUpdate: (value) { + _attachments[idx]!.metadata ??= {}; + _attachments[idx]!.metadata!['thumbnail'] = value; + }, + ), + ); + } + void _showEdit(Attachment element, int index) { showDialog( context: context, @@ -455,11 +471,12 @@ class _AttachmentEditorPopupState extends State { ); } - Widget _buildListEntry(Attachment element, int index) { + Widget _buildListEntry(Attachment element, int idx) { var fileType = element.mimetype.split('/').firstOrNull; fileType ??= 'unknown'; final canBePreview = fileType.toLowerCase() == 'image'; + final canHasThumbnail = fileType.toLowerCase() != 'image'; return Container( padding: const EdgeInsets.only(left: 16, right: 8, bottom: 16), @@ -491,14 +508,23 @@ class _AttachmentEditorPopupState extends State { ], ), ), - IconButton( - color: Colors.teal, - icon: const Icon(Icons.preview), - visualDensity: const VisualDensity(horizontal: -4), - onPressed: canBePreview - ? () => _showAttachmentPreview(element) - : null, - ), + if (canBePreview) + IconButton( + color: Colors.teal, + icon: const Icon(Icons.preview), + visualDensity: const VisualDensity(horizontal: -4), + onPressed: () => _showAttachmentPreview(element), + ), + if (canHasThumbnail) + IconButton( + color: Colors.teal, + icon: const Icon(Icons.add_photo_alternate), + visualDensity: const VisualDensity(horizontal: -4), + onPressed: () => _showAttachmentThumbnailEditor( + element, + idx, + ), + ), PopupMenuButton( icon: const Icon(Icons.more_horiz), iconColor: Theme.of(context).colorScheme.primary, @@ -514,7 +540,7 @@ class _AttachmentEditorPopupState extends State { horizontal: 8, ), ), - onTap: () => _showEdit(element, index), + onTap: () => _showEdit(element, idx), ), PopupMenuItem( child: ListTile( @@ -527,7 +553,7 @@ class _AttachmentEditorPopupState extends State { onTap: () { _deleteAttachment(element).then((_) { widget.onRemove(element.rid); - setState(() => _attachments.removeAt(index)); + setState(() => _attachments.removeAt(idx)); }); }, ), @@ -541,7 +567,7 @@ class _AttachmentEditorPopupState extends State { ), onTap: () { widget.onRemove(element.rid); - setState(() => _attachments.removeAt(index)); + setState(() => _attachments.removeAt(idx)); }, ), ], diff --git a/lib/widgets/attachments/attachment_editor_thumbnail.dart b/lib/widgets/attachments/attachment_editor_thumbnail.dart new file mode 100644 index 0000000..2483820 --- /dev/null +++ b/lib/widgets/attachments/attachment_editor_thumbnail.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:solian/exts.dart'; +import 'package:solian/models/attachment.dart'; +import 'package:solian/providers/content/attachment.dart'; +import 'package:solian/widgets/attachments/attachment_editor.dart'; + +class AttachmentEditorThumbnailDialog extends StatefulWidget { + final Attachment item; + final String pool; + final String? initialItem; + final Function(String? id) onUpdate; + + const AttachmentEditorThumbnailDialog({ + super.key, + required this.item, + required this.pool, + required this.initialItem, + required this.onUpdate, + }); + + @override + State createState() => + _AttachmentEditorThumbnailDialogState(); +} + +class _AttachmentEditorThumbnailDialogState + extends State { + bool _isLoading = false; + + final TextEditingController _attachmentController = TextEditingController(); + + void _promptUploadNewAttachment() { + showModalBottomSheet( + context: context, + builder: (context) => AttachmentEditorPopup( + pool: widget.pool, + singleMode: true, + imageOnly: true, + autoUpload: true, + onAdd: (value) { + widget.onUpdate(value); + _attachmentController.text = value; + }, + initialAttachments: const [], + onRemove: (_) {}, + ), + ); + } + + Future _updateAttachment() async { + setState(() => _isLoading = true); + + final AttachmentProvider attach = Get.find(); + + widget.item.metadata ??= {}; + widget.item.metadata!['thumbnail'] = _attachmentController.text; + + try { + await attach.updateAttachment( + widget.item.id, + widget.item.alt, + isMature: widget.item.isMature, + metadata: widget.item.metadata!, + ); + + Get.find().clearCache(id: widget.item.rid); + } catch (e) { + context.showErrorDialog(e); + } finally { + setState(() => _isLoading = false); + } + } + + @override + void initState() { + if (widget.initialItem != null) { + _attachmentController.text = widget.initialItem!; + } + super.initState(); + } + + @override + void dispose() { + _attachmentController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text('postThumbnail'.tr), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Card( + margin: EdgeInsets.zero, + child: ListTile( + title: Text('postThumbnailAttachmentNew'.tr), + contentPadding: const EdgeInsets.only(left: 12, right: 9), + trailing: const Icon(Icons.chevron_right), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + onTap: () { + _promptUploadNewAttachment(); + }, + ), + ), + const Row(children: [ + Expanded(child: Divider()), + Text('OR'), + Expanded(child: Divider()), + ]).paddingOnly(top: 12, bottom: 16, left: 16, right: 16), + TextField( + controller: _attachmentController, + decoration: InputDecoration( + isDense: true, + border: const OutlineInputBorder(), + prefixText: '#', + labelText: 'postThumbnailAttachment'.tr, + ), + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + ), + ], + ), + actions: [ + TextButton( + onPressed: _isLoading + ? null + : () { + _updateAttachment().then((_) { + widget.onUpdate(_attachmentController.text); + if (mounted) { + Navigator.pop(context); + } + }); + }, + child: Text('confirm'.tr), + ), + ], + ); + } +} diff --git a/lib/widgets/posts/post_item.dart b/lib/widgets/posts/post_item.dart index fddb904..3e20a69 100644 --- a/lib/widgets/posts/post_item.dart +++ b/lib/widgets/posts/post_item.dart @@ -299,14 +299,14 @@ class _PostItemState extends State { return AttachmentList( parentId: widget.item.id.toString(), attachmentsId: attachments, - autoload: true, + autoload: false, isGrid: true, ).paddingOnly(left: 36, top: 4, bottom: 4); } else if (attachments.length > 1) { return AttachmentList( parentId: widget.item.id.toString(), attachmentsId: attachments, - autoload: true, + autoload: false, isColumn: true, ).paddingOnly(left: 60, right: 24); } else { @@ -314,7 +314,7 @@ class _PostItemState extends State { flatMaxHeight: MediaQuery.of(context).size.width, parentId: widget.item.id.toString(), attachmentsId: attachments, - autoload: true, + autoload: false, ); } } diff --git a/pubspec.yaml b/pubspec.yaml index e09d9ef..6868099 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: solian description: "The Solar Network App" publish_to: "none" -version: 1.2.1+30 +version: 1.2.1+32 environment: sdk: ">=3.3.4 <4.0.0"