diff --git a/assets/locales/en_us.json b/assets/locales/en_us.json index 347d09d..4f536f7 100644 --- a/assets/locales/en_us.json +++ b/assets/locales/en_us.json @@ -157,6 +157,9 @@ "postListNews": "News", "postListFriends": "Friends", "postListShuffle": "Random", + "attachmentThumbnail": "Thumbnail", + "attachmentThumbnailAttachmentNew": "Upload thumbnail", + "attachmentThumbnailAttachment": "Attachment serial number", "postEditorModeStory": "Post a post", "postEditorModeArticle": "Post an article", "postEditor": "Post editor", diff --git a/assets/locales/zh_cn.json b/assets/locales/zh_cn.json index 1d2e78a..c1ea4f6 100644 --- a/assets/locales/zh_cn.json +++ b/assets/locales/zh_cn.json @@ -168,6 +168,9 @@ "postListNews": "新鲜事", "postListFriends": "好友圈", "postListShuffle": "打乱看", + "attachmentThumbnail": "附件缩略图", + "attachmentThumbnailAttachmentNew": "上传附件作为缩略图", + "attachmentThumbnailAttachment": "附件序列号", "postNew": "创建新帖子", "postNewInRealmHint": "在领域 @realm 里发表新帖子", "postAction": "发表", diff --git a/lib/bootstrapper.dart b/lib/bootstrapper.dart index 3ca9646..ae74a32 100644 --- a/lib/bootstrapper.dart +++ b/lib/bootstrapper.dart @@ -9,7 +9,6 @@ import 'package:solian/providers/auth.dart'; import 'package:solian/providers/content/channel.dart'; import 'package:solian/providers/content/realm.dart'; import 'package:solian/providers/relation.dart'; -import 'package:solian/providers/stickers.dart'; import 'package:solian/providers/theme_switcher.dart'; import 'package:solian/providers/websocket.dart'; import 'package:solian/services.dart'; @@ -115,7 +114,6 @@ class _BootstrapperShellState extends State { final AuthProvider auth = Get.find(); try { await Future.wait([ - Get.find().refreshAvailableStickers(), if (auth.isAuthorized.isTrue) Get.find().refreshAvailableChannel(), if (auth.isAuthorized.isTrue) diff --git a/lib/providers/stickers.dart b/lib/providers/stickers.dart index bbe0fc8..de89197 100644 --- a/lib/providers/stickers.dart +++ b/lib/providers/stickers.dart @@ -1,4 +1,5 @@ import 'package:get/get.dart'; +import 'package:solian/exceptions/request.dart'; import 'package:solian/models/pagination.dart'; import 'package:solian/models/stickers.dart'; import 'package:solian/services.dart'; @@ -31,4 +32,16 @@ class StickerProvider extends GetxController { } availableStickers.refresh(); } + + Future getStickerByAlias(String alias) async { + final client = await ServiceFinder.configureClient('files'); + final resp = await client.get( + '/stickers/lookup/$alias', + ); + if (resp.statusCode != 200) { + throw RequestException(resp); + } + + return Sticker.fromJson(resp.body); + } } diff --git a/lib/router.dart b/lib/router.dart index ed0c727..d74594f 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -7,7 +7,6 @@ import 'package:solian/screens/account.dart'; import 'package:solian/screens/account/friend.dart'; import 'package:solian/screens/account/personalize.dart'; import 'package:solian/screens/account/profile_page.dart'; -import 'package:solian/screens/account/stickers.dart'; import 'package:solian/screens/auth/signin.dart'; import 'package:solian/screens/auth/signup.dart'; import 'package:solian/screens/channel/channel_chat.dart'; @@ -238,14 +237,6 @@ abstract class AppRouter { name: 'accountFriend', builder: (context, state) => const FriendScreen(), ), - GoRoute( - path: '/account/stickers', - name: 'accountStickers', - builder: (context, state) => TitleShell( - state: state, - child: const StickerScreen(), - ), - ), GoRoute( path: '/account/personalize', name: 'accountProfile', diff --git a/lib/screens/account.dart b/lib/screens/account.dart index 88b4b64..6788ce0 100644 --- a/lib/screens/account.dart +++ b/lib/screens/account.dart @@ -45,11 +45,6 @@ class _AccountScreenState extends State { 'accountFriend'.tr, 'accountFriend', ), - ( - const Icon(Icons.emoji_symbols), - 'accountStickers'.tr, - 'accountStickers', - ), ]; final AuthProvider auth = Get.find(); diff --git a/lib/screens/account/stickers.dart b/lib/screens/account/stickers.dart deleted file mode 100644 index 0e865cd..0000000 --- a/lib/screens/account/stickers.dart +++ /dev/null @@ -1,186 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:gap/gap.dart'; -import 'package:get/get.dart'; -import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; -import 'package:solian/models/pagination.dart'; -import 'package:solian/models/stickers.dart'; -import 'package:solian/providers/auth.dart'; -import 'package:solian/providers/stickers.dart'; -import 'package:solian/services.dart'; -import 'package:solian/widgets/auto_cache_image.dart'; -import 'package:solian/widgets/stickers/sticker_uploader.dart'; - -class StickerScreen extends StatefulWidget { - const StickerScreen({super.key}); - - @override - State createState() => _StickerScreenState(); -} - -class _StickerScreenState extends State { - final PagingController _pagingController = - PagingController(firstPageKey: 0); - - Future _promptDelete(Sticker item, String prefix) async { - final AuthProvider auth = Get.find(); - if (auth.isAuthorized.isFalse) return false; - - final confirm = await showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text('stickerDeletionConfirm'.tr), - content: Text( - 'stickerDeletionConfirmCaption'.trParams({ - 'name': ':${'$prefix${item.alias}'.camelCase}:', - }), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text('cancel'.tr), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: Text('confirm'.tr), - ), - ], - ), - ); - if (confirm != true) return false; - - final client = await auth.configureClient('files'); - final resp = await client.delete('/stickers/${item.id}'); - - return resp.statusCode == 200; - } - - Future _promptUploadSticker({Sticker? edit}) { - return showDialog( - context: context, - builder: (context) => StickerUploadDialog( - edit: edit, - ), - ); - } - - Widget _buildEmoteEntry(Sticker item, String prefix) { - final imageUrl = ServiceFinder.buildUrl( - 'files', - '/attachments/${item.attachment.rid}', - ); - return ListTile( - title: Text(item.name), - subtitle: Text(item.textWarpedPlaceholder), - contentPadding: const EdgeInsets.only(left: 16, right: 14), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon(Icons.edit_square), - onPressed: () { - _promptUploadSticker(edit: item).then((value) { - if (value == true) _pagingController.refresh(); - }); - }, - ), - IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - _promptDelete(item, prefix).then((value) { - if (value == true) _pagingController.refresh(); - }); - }, - ), - ], - ), - leading: AutoCacheImage( - imageUrl, - width: 28, - height: 28, - noErrorWidget: true, - ), - ); - } - - @override - void initState() { - final AuthProvider auth = Get.find(); - final name = auth.userProfile.value!['name']; - _pagingController.addPageRequestListener((pageKey) async { - final client = await ServiceFinder.configureClient('files'); - final resp = await client.get( - '/stickers/manifest?take=10&offset=$pageKey&author=$name', - ); - if (resp.statusCode == 200) { - final result = PaginationResult.fromJson(resp.body); - final out = result.data?.map((e) => StickerPack.fromJson(e)).toList(); - if (out != null && result.data!.length >= 10) { - _pagingController.appendPage(out, pageKey + out.length); - } else if (out != null) { - _pagingController.appendLastPage(out); - } - } else { - _pagingController.error = resp.bodyString; - } - }); - super.initState(); - } - - @override - void dispose() { - final StickerProvider sticker = Get.find(); - sticker.refreshAvailableStickers(); - _pagingController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - floatingActionButton: FloatingActionButton( - child: const Icon(Icons.add), - onPressed: () { - _promptUploadSticker().then((value) { - if (value == true) _pagingController.refresh(); - }); - }, - ), - body: RefreshIndicator( - onRefresh: () => Future.sync(() => _pagingController.refresh()), - child: CustomScrollView( - slivers: [ - PagedSliverList( - pagingController: _pagingController, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (BuildContext context, item, int index) { - return ExpansionTile( - title: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text(item.name), - const Gap(6), - Badge( - label: Text('#${item.id}'), - ) - ], - ), - subtitle: Text( - item.description, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - children: item.stickers?.map((x) { - x.pack = item; - return _buildEmoteEntry(x, item.prefix); - }).toList() ?? - List.empty(), - ); - }, - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/widgets/attachments/attachment_editor_thumbnail.dart b/lib/widgets/attachments/attachment_editor_thumbnail.dart index f07a43a..6bdbd23 100644 --- a/lib/widgets/attachments/attachment_editor_thumbnail.dart +++ b/lib/widgets/attachments/attachment_editor_thumbnail.dart @@ -93,14 +93,14 @@ class _AttachmentEditorThumbnailDialogState @override Widget build(BuildContext context) { return AlertDialog( - title: Text('postThumbnail'.tr), + title: Text('attachmentThumbnail'.tr), content: Column( mainAxisSize: MainAxisSize.min, children: [ Card( margin: EdgeInsets.zero, child: ListTile( - title: Text('postThumbnailAttachmentNew'.tr), + title: Text('attachmentThumbnailAttachmentNew'.tr), contentPadding: const EdgeInsets.only(left: 12, right: 9), trailing: const Icon(Icons.chevron_right), shape: const RoundedRectangleBorder( @@ -122,7 +122,7 @@ class _AttachmentEditorThumbnailDialogState isDense: true, border: const OutlineInputBorder(), prefixText: '#', - labelText: 'postThumbnailAttachment'.tr, + labelText: 'attachmentThumbnailAttachment'.tr, ), onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), ), diff --git a/lib/widgets/posts/editor/post_editor_thumbnail.dart b/lib/widgets/posts/editor/post_editor_thumbnail.dart index 6ec3597..783543e 100644 --- a/lib/widgets/posts/editor/post_editor_thumbnail.dart +++ b/lib/widgets/posts/editor/post_editor_thumbnail.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:gap/gap.dart'; import 'package:get/get.dart'; import 'package:solian/controllers/post_editor_controller.dart'; import 'package:solian/widgets/attachments/attachment_editor.dart'; @@ -58,18 +57,25 @@ class _PostEditorThumbnailDialogState extends State { content: Column( mainAxisSize: MainAxisSize.min, children: [ - ListTile( - title: Text('postThumbnailAttachmentNew'.tr), - contentPadding: const EdgeInsets.only(left: 16, right: 13), - trailing: const Icon(Icons.chevron_right), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(8)), + 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(); + }, ), - onTap: () { - _promptUploadNewAttachment(); - }, ), - const Gap(8), + 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( diff --git a/lib/widgets/stickers/sticker_uploader.dart b/lib/widgets/stickers/sticker_uploader.dart deleted file mode 100644 index 4f9b834..0000000 --- a/lib/widgets/stickers/sticker_uploader.dart +++ /dev/null @@ -1,212 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:gap/gap.dart'; -import 'package:get/get.dart'; -import 'package:solian/exts.dart'; -import 'package:solian/models/stickers.dart'; -import 'package:solian/providers/auth.dart'; -import 'package:solian/widgets/attachments/attachment_editor.dart'; - -class StickerUploadDialog extends StatefulWidget { - final Sticker? edit; - - const StickerUploadDialog({super.key, this.edit}); - - @override - State createState() => _StickerUploadDialogState(); -} - -class _StickerUploadDialogState extends State { - final TextEditingController _attachmentController = TextEditingController(); - final TextEditingController _packController = TextEditingController(); - final TextEditingController _aliasController = TextEditingController(); - final TextEditingController _nameController = TextEditingController(); - - Color get _unFocusColor => - Theme.of(context).colorScheme.onSurface.withOpacity(0.75); - - bool _isBusy = false; - - void _promptUploadNewAttachment() { - showModalBottomSheet( - context: context, - builder: (context) => AttachmentEditorPopup( - pool: 'sticker', - singleMode: true, - imageOnly: true, - autoUpload: true, - imageMaxHeight: 28, - imageMaxWidth: 28, - onAdd: (value) { - setState(() { - _attachmentController.text = value.toString(); - }); - }, - initialAttachments: const [], - onRemove: (_) {}, - ), - ); - } - - Future _applySticker() async { - final AuthProvider auth = Get.find(); - if (auth.isAuthorized.isFalse) return; - - if ([ - _nameController.text.isEmpty, - _aliasController.text.isEmpty, - _packController.text.isEmpty, - _attachmentController.text.isEmpty, - ].any((x) => x)) { - return; - } - - setState(() => _isBusy = true); - - Response resp; - final client = await auth.configureClient('files'); - if (widget.edit == null) { - resp = await client.post('/stickers', { - 'name': _nameController.text, - 'alias': _aliasController.text, - 'pack_id': int.tryParse(_packController.text), - 'attachment_id': int.tryParse(_attachmentController.text), - }); - } else { - resp = await client.put('/stickers/${widget.edit!.id}', { - 'name': _nameController.text, - 'alias': _aliasController.text, - 'pack_id': int.tryParse(_packController.text), - 'attachment_id': int.tryParse(_attachmentController.text), - }); - } - - setState(() => _isBusy = false); - - if (resp.statusCode != 200) { - context.showErrorDialog(resp.bodyString); - } else { - Navigator.pop(context, true); - } - } - - @override - void initState() { - super.initState(); - if (widget.edit != null) { - _attachmentController.text = widget.edit!.attachmentId.toString(); - _packController.text = widget.edit!.packId.toString(); - _aliasController.text = widget.edit!.alias; - _nameController.text = widget.edit!.name; - } - } - - @override - void dispose() { - _attachmentController.dispose(); - _packController.dispose(); - _aliasController.dispose(); - _nameController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return AlertDialog( - title: Text('stickerUploader'.tr), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListTile( - title: Text('stickerUploaderAttachmentNew'.tr), - contentPadding: const EdgeInsets.only(left: 16, right: 13), - trailing: const Icon(Icons.chevron_right), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(8)), - ), - onTap: () { - _promptUploadNewAttachment(); - }, - ), - const Gap(8), - TextField( - controller: _attachmentController, - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - prefixText: '#', - labelText: 'stickerUploaderAttachment'.tr, - ), - onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), - ), - const Gap(8), - TextField( - controller: _packController, - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - prefixText: '#', - labelText: 'stickerUploaderPack'.tr, - ), - onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), - ), - Container( - padding: - const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 6), - child: Text( - 'stickerUploaderPackHint'.tr, - style: TextStyle(color: _unFocusColor), - ), - ), - TextField( - controller: _aliasController, - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - labelText: 'stickerUploaderAlias'.tr, - ), - onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), - ), - Container( - padding: - const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 6), - child: Text( - 'stickerUploaderAliasHint'.tr, - style: TextStyle(color: _unFocusColor), - ), - ), - TextField( - controller: _nameController, - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - labelText: 'stickerUploaderName'.tr, - ), - onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), - ), - Container( - padding: const EdgeInsets.only(left: 8, right: 8, top: 4), - child: Text( - 'stickerUploaderNameHint'.tr, - style: TextStyle(color: _unFocusColor), - ), - ), - ], - ), - actions: [ - TextButton( - style: TextButton.styleFrom( - foregroundColor: - Theme.of(context).colorScheme.onSurface.withOpacity(0.8), - ), - onPressed: _isBusy ? null : () => Navigator.pop(context), - child: Text('cancel'.tr), - ), - TextButton( - onPressed: _isBusy ? null : () => _applySticker(), - child: Text('apply'.tr), - ), - ], - ); - } -}