diff --git a/assets/translations/en-US.json b/assets/translations/en-US.json index 001d012..21815f6 100644 --- a/assets/translations/en-US.json +++ b/assets/translations/en-US.json @@ -281,15 +281,18 @@ "one": "{} attachment", "other": "{} attachments" }, + "fieldAttachmentRandomId": "Random ID", "addAttachmentFromAlbum": "Add from album", "addAttachmentFromClipboard": "Paste file", "addAttachmentFromCameraPhoto": "Take photo", "addAttachmentFromCameraVideo": "Take video", + "addAttachmentFromRandomId": "Link via RID", "attachmentPastedImage": "Pasted Image", "attachmentInsertLink": "Insert Link", "attachmentSetAsPostThumbnail": "Set as post thumbnail", "attachmentUnsetAsPostThumbnail": "Unset as post thumbnail", "attachmentSetThumbnail": "Set thumbnail", + "attachmentCopyRandomId": "Copy RID", "attachmentUpload": "Upload", "notification": "Notification", "notificationUnreadCount": { diff --git a/assets/translations/zh-CN.json b/assets/translations/zh-CN.json index a90afa8..3c7e737 100644 --- a/assets/translations/zh-CN.json +++ b/assets/translations/zh-CN.json @@ -279,15 +279,18 @@ "one": "{} 个附件", "other": "{} 个附件" }, + "fieldAttachmentRandomId": "访问 ID", "addAttachmentFromAlbum": "从相册中添加附件", "addAttachmentFromClipboard": "粘贴附件", "addAttachmentFromCameraPhoto": "拍摄照片", "addAttachmentFromCameraVideo": "拍摄视频", + "addAttachmentFromRandomId": "通过访问 ID 链接", "attachmentPastedImage": "粘贴的图片", "attachmentInsertLink": "插入连接", "attachmentSetAsPostThumbnail": "设置为帖子缩略图", "attachmentUnsetAsPostThumbnail": "取消设置为帖子缩略图", "attachmentSetThumbnail": "设置缩略图", + "attachmentCopyRandomId": "复制访问 ID", "attachmentUpload": "上传", "notification": "通知", "notificationUnreadCount": { diff --git a/lib/providers/userinfo.dart b/lib/providers/userinfo.dart index d8010b8..255a14f 100644 --- a/lib/providers/userinfo.dart +++ b/lib/providers/userinfo.dart @@ -31,9 +31,10 @@ class UserProvider extends ChangeNotifier { final value = _config.prefs.getString(kAtkStoreKey); isAuthorized = value != null; notifyListeners(); - refreshUser().then((value) { + refreshUser().then((value) async { if (value != null) { log('Logged in as @${value.name}'); + log('Atk: ${await atk}'); } }); } diff --git a/lib/screens/post/post_editor.dart b/lib/screens/post/post_editor.dart index 1f8b45d..61081a1 100644 --- a/lib/screens/post/post_editor.dart +++ b/lib/screens/post/post_editor.dart @@ -96,38 +96,6 @@ class _PostEditorScreenState extends State { ); } - final _imagePicker = ImagePicker(); - - void _takeMedia(bool isVideo) async { - final result = isVideo - ? await _imagePicker.pickVideo(source: ImageSource.camera) - : await _imagePicker.pickImage(source: ImageSource.camera); - if (result == null) return; - _writeController.addAttachments([ - PostWriteMedia.fromFile(result), - ]); - } - - void _selectMedia() async { - final result = await _imagePicker.pickMultipleMedia(); - if (result.isEmpty) return; - _writeController.addAttachments( - result.map((e) => PostWriteMedia.fromFile(e)), - ); - } - - void _pasteMedia() async { - final imageBytes = await Pasteboard.image; - if (imageBytes == null) return; - _writeController.addAttachments([ - PostWriteMedia.fromBytes( - imageBytes, - 'attachmentPastedImage'.tr(), - PostWriteMediaType.image, - ), - ]); - } - @override void dispose() { _writeController.dispose(); @@ -435,63 +403,12 @@ class _PostEditorScreenState extends State { scrollDirection: Axis.vertical, child: Row( children: [ - PopupMenuButton( - icon: Icon( - Symbols.add_photo_alternate, - color: Theme.of(context).colorScheme.primary, - ), - itemBuilder: (context) => [ - if (!kIsWeb && !Platform.isLinux && !Platform.isMacOS && !Platform.isWindows) - PopupMenuItem( - child: Row( - children: [ - const Icon(Symbols.photo_camera), - const Gap(16), - Text('addAttachmentFromCameraPhoto').tr(), - ], - ), - onTap: () { - _takeMedia(false); - }, - ), - if (!kIsWeb && !Platform.isLinux && !Platform.isMacOS && !Platform.isWindows) - PopupMenuItem( - child: Row( - children: [ - const Icon(Symbols.videocam), - const Gap(16), - Text('addAttachmentFromCameraVideo').tr(), - ], - ), - onTap: () { - _takeMedia(true); - }, - ), - PopupMenuItem( - child: Row( - children: [ - const Icon(Symbols.photo_library), - const Gap(16), - Text('addAttachmentFromAlbum').tr(), - ], - ), - onTap: () { - _selectMedia(); - }, - ), - PopupMenuItem( - child: Row( - children: [ - const Icon(Symbols.content_paste), - const Gap(16), - Text('addAttachmentFromClipboard').tr(), - ], - ), - onTap: () { - _pasteMedia(); - }, - ), - ], + AddPostMediaButton( + onAdd: (items) { + setState(() { + _writeController.addAttachments(items); + }); + }, ), ], ), diff --git a/lib/widgets/chat/chat_message_input.dart b/lib/widgets/chat/chat_message_input.dart index 6a24520..b98f6dc 100644 --- a/lib/widgets/chat/chat_message_input.dart +++ b/lib/widgets/chat/chat_message_input.dart @@ -123,40 +123,6 @@ class ChatMessageInputState extends State { } final List _attachments = List.empty(growable: true); - final _imagePicker = ImagePicker(); - - void _takeMedia(bool isVideo) async { - final result = isVideo - ? await _imagePicker.pickVideo(source: ImageSource.camera) - : await _imagePicker.pickImage(source: ImageSource.camera); - if (result == null) return; - _attachments.add( - PostWriteMedia.fromFile(result), - ); - setState(() {}); - } - - void _selectMedia() async { - final result = await _imagePicker.pickMultipleMedia(); - if (result.isEmpty) return; - _attachments.addAll( - result.map((e) => PostWriteMedia.fromFile(e)), - ); - setState(() {}); - } - - void _pasteMedia() async { - final imageBytes = await Pasteboard.image; - if (imageBytes == null) return; - _attachments.add( - PostWriteMedia.fromBytes( - imageBytes, - 'attachmentPastedImage'.tr(), - PostWriteMediaType.image, - ), - ); - setState(() {}); - } @override void dispose() { @@ -294,63 +260,12 @@ class ChatMessageInputState extends State { ), ), const Gap(8), - PopupMenuButton( - icon: Icon( - Symbols.add_photo_alternate, - color: Theme.of(context).colorScheme.primary, - ), - itemBuilder: (context) => [ - if (!kIsWeb && !Platform.isLinux && !Platform.isMacOS && !Platform.isWindows) - PopupMenuItem( - child: Row( - children: [ - const Icon(Symbols.photo_camera), - const Gap(16), - Text('addAttachmentFromCameraPhoto').tr(), - ], - ), - onTap: () { - _takeMedia(false); - }, - ), - if (!kIsWeb && !Platform.isLinux && !Platform.isMacOS && !Platform.isWindows) - PopupMenuItem( - child: Row( - children: [ - const Icon(Symbols.videocam), - const Gap(16), - Text('addAttachmentFromCameraVideo').tr(), - ], - ), - onTap: () { - _takeMedia(true); - }, - ), - PopupMenuItem( - child: Row( - children: [ - const Icon(Symbols.photo_library), - const Gap(16), - Text('addAttachmentFromAlbum').tr(), - ], - ), - onTap: () { - _selectMedia(); - }, - ), - PopupMenuItem( - child: Row( - children: [ - const Icon(Symbols.content_paste), - const Gap(16), - Text('addAttachmentFromClipboard').tr(), - ], - ), - onTap: () { - _pasteMedia(); - }, - ), - ], + AddPostMediaButton( + onAdd: (items) { + setState(() { + _attachments.addAll(items); + }); + }, ), IconButton( onPressed: _isBusy ? null : _sendMessage, diff --git a/lib/widgets/post/post_media_pending_list.dart b/lib/widgets/post/post_media_pending_list.dart index 317529c..1b91e8e 100644 --- a/lib/widgets/post/post_media_pending_list.dart +++ b/lib/widgets/post/post_media_pending_list.dart @@ -6,12 +6,16 @@ import 'package:dismissible_page/dismissible_page.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_context_menu/flutter_context_menu.dart'; import 'package:gap/gap.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:material_symbols_icons/symbols.dart'; +import 'package:pasteboard/pasteboard.dart'; import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:surface/controllers/post_write_controller.dart'; +import 'package:surface/providers/sn_attachment.dart'; import 'package:surface/providers/sn_network.dart'; import 'package:surface/widgets/attachment/attachment_zoom.dart'; import 'package:surface/widgets/context_menu.dart'; @@ -91,6 +95,12 @@ class PostMediaPendingList extends StatelessWidget { ContextMenu _createContextMenu(BuildContext context, int idx, PostWriteMedia media) { return ContextMenu( entries: [ + if (media.attachment != null && media.type == PostWriteMediaType.video) + MenuItem( + label: 'attachmentSetThumbnail'.tr(), + icon: Symbols.image, + onSelected: () {}, + ), if (media.attachment == null && onUpload != null) MenuItem( label: 'attachmentUpload'.tr(), @@ -98,7 +108,10 @@ class PostMediaPendingList extends StatelessWidget { onSelected: () { onUpload!(idx); }), - if (media.attachment != null && onPostSetThumbnail != null && idx != -1) + if (media.attachment != null && + media.type == PostWriteMediaType.image && + onPostSetThumbnail != null && + idx != -1) MenuItem( label: 'attachmentSetAsPostThumbnail'.tr(), icon: Symbols.gallery_thumbnail, @@ -139,6 +152,14 @@ class PostMediaPendingList extends StatelessWidget { icon: Symbols.crop, onSelected: () => _cropImage(context, idx), ), + if (media.attachment != null) + MenuItem( + label: 'attachmentCopyRandomId'.tr(), + icon: Symbols.content_copy, + onSelected: () { + Clipboard.setData(ClipboardData(text: media.attachment!.rid)); + }, + ), if (media.attachment != null && onRemove != null) MenuItem( label: 'delete'.tr(), @@ -191,8 +212,8 @@ class PostMediaPendingList extends StatelessWidget { aspectRatio: 1, child: switch (thumbnail!.type) { PostWriteMediaType.image => Container( - color: Theme.of(context).colorScheme.surfaceContainer, - child: LayoutBuilder(builder: (context, constraints) { + color: Theme.of(context).colorScheme.surfaceContainer, + child: LayoutBuilder(builder: (context, constraints) { return Image( image: thumbnail!.getImageProvider( context, @@ -202,7 +223,7 @@ class PostMediaPendingList extends StatelessWidget { fit: BoxFit.contain, ); }), - ), + ), _ => Container( color: Theme.of(context).colorScheme.surface, child: const Icon(Symbols.docs).center(), @@ -241,8 +262,8 @@ class PostMediaPendingList extends StatelessWidget { aspectRatio: 1, child: switch (media.type) { PostWriteMediaType.image => Container( - color: Theme.of(context).colorScheme.surfaceContainer, - child: LayoutBuilder(builder: (context, constraints) { + color: Theme.of(context).colorScheme.surfaceContainer, + child: LayoutBuilder(builder: (context, constraints) { return Image( image: media.getImageProvider( context, @@ -252,7 +273,11 @@ class PostMediaPendingList extends StatelessWidget { fit: BoxFit.contain, ); }), - ), + ), + PostWriteMediaType.video => Container( + color: Theme.of(context).colorScheme.surfaceContainer, + child: const Icon(Symbols.videocam).center(), + ), _ => Container( color: Theme.of(context).colorScheme.surfaceContainer, child: const Icon(Symbols.docs).center(), @@ -270,3 +295,162 @@ class PostMediaPendingList extends StatelessWidget { ); } } + +class AddPostMediaButton extends StatelessWidget { + final Function(Iterable) onAdd; + + const AddPostMediaButton({super.key, required this.onAdd}); + + void _takeMedia(bool isVideo) async { + final picker = ImagePicker(); + final result = isVideo + ? await picker.pickVideo(source: ImageSource.camera) + : await picker.pickImage(source: ImageSource.camera); + if (result == null) return; + onAdd([PostWriteMedia.fromFile(result)]); + } + + void _selectMedia() async { + final picker = ImagePicker(); + final result = await picker.pickMultipleMedia(); + if (result.isEmpty) return; + onAdd( + result.map((e) => PostWriteMedia.fromFile(e)), + ); + } + + void _pasteMedia() async { + final imageBytes = await Pasteboard.image; + if (imageBytes == null) return; + onAdd([ + PostWriteMedia.fromBytes( + imageBytes, + 'attachmentPastedImage'.tr(), + PostWriteMediaType.image, + ), + ]); + } + + void _linkRandomId(BuildContext context) async { + final randomIdController = TextEditingController(); + final randomId = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('addAttachmentFromRandomId').tr(), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: randomIdController, + decoration: InputDecoration( + labelText: 'fieldAttachmentRandomId'.tr(), + border: const UnderlineInputBorder(), + ), + ), + const Gap(8), + ], + ), + actions: [ + TextButton( + child: Text('dialogDismiss').tr(), + onPressed: () { + Navigator.pop(context); + }, + ), + TextButton( + child: Text('dialogConfirm').tr(), + onPressed: () { + Navigator.pop(context, randomIdController.text); + }, + ), + ], + ), + ); + WidgetsBinding.instance.addPostFrameCallback((_) { + randomIdController.dispose(); + }); + if (randomId == null || randomId.isEmpty) return; + if (!context.mounted) return; + + final attach = context.read(); + final attachment = await attach.getOne(randomId); + + onAdd([ + PostWriteMedia(attachment), + ]); + } + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + icon: Icon( + Symbols.add_photo_alternate, + color: Theme.of(context).colorScheme.primary, + ), + itemBuilder: (context) => [ + if (!kIsWeb && !Platform.isLinux && !Platform.isMacOS && !Platform.isWindows) + PopupMenuItem( + child: Row( + children: [ + const Icon(Symbols.photo_camera), + const Gap(16), + Text('addAttachmentFromCameraPhoto').tr(), + ], + ), + onTap: () { + _takeMedia(false); + }, + ), + if (!kIsWeb && !Platform.isLinux && !Platform.isMacOS && !Platform.isWindows) + PopupMenuItem( + child: Row( + children: [ + const Icon(Symbols.videocam), + const Gap(16), + Text('addAttachmentFromCameraVideo').tr(), + ], + ), + onTap: () { + _takeMedia(true); + }, + ), + PopupMenuItem( + child: Row( + children: [ + const Icon(Symbols.photo_library), + const Gap(16), + Text('addAttachmentFromAlbum').tr(), + ], + ), + onTap: () { + _selectMedia(); + }, + ), + PopupMenuItem( + child: Row( + children: [ + const Icon(Symbols.link), + const Gap(16), + Text('addAttachmentFromRandomId').tr(), + ], + ), + onTap: () { + _linkRandomId(context); + }, + ), + PopupMenuItem( + child: Row( + children: [ + const Icon(Symbols.content_paste), + const Gap(16), + Text('addAttachmentFromClipboard').tr(), + ], + ), + onTap: () { + _pasteMedia(); + }, + ), + ], + ); + } +}