Thumbnail

This commit is contained in:
LittleSheep 2024-08-11 01:57:58 +08:00
parent 4a3e6a9e15
commit 22026efa7d
7 changed files with 173 additions and 7 deletions

View File

@ -10,6 +10,7 @@ import 'package:solian/widgets/posts/editor/post_editor_categories_tags.dart';
import 'package:solian/widgets/posts/editor/post_editor_date.dart'; import 'package:solian/widgets/posts/editor/post_editor_date.dart';
import 'package:solian/widgets/posts/editor/post_editor_overview.dart'; import 'package:solian/widgets/posts/editor/post_editor_overview.dart';
import 'package:solian/widgets/posts/editor/post_editor_publish_zone.dart'; import 'package:solian/widgets/posts/editor/post_editor_publish_zone.dart';
import 'package:solian/widgets/posts/editor/post_editor_thumbnail.dart';
import 'package:solian/widgets/posts/editor/post_editor_visibility.dart'; import 'package:solian/widgets/posts/editor/post_editor_visibility.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -31,6 +32,7 @@ class PostEditorController extends GetxController {
Rx<DateTime?> publishedUntil = Rx(null); Rx<DateTime?> publishedUntil = Rx(null);
RxList<int> attachments = RxList<int>.empty(growable: true); RxList<int> attachments = RxList<int>.empty(growable: true);
RxList<String> tags = RxList<String>.empty(growable: true); RxList<String> tags = RxList<String>.empty(growable: true);
Rx<int?> thumbnail = Rx(null);
RxList<int> visibleUsers = RxList.empty(growable: true); RxList<int> visibleUsers = RxList.empty(growable: true);
RxList<int> invisibleUsers = RxList.empty(growable: true); RxList<int> invisibleUsers = RxList.empty(growable: true);
@ -125,6 +127,15 @@ class PostEditorController extends GetxController {
); );
} }
Future<void> editThumbnail(BuildContext context) {
return showDialog(
context: context,
builder: (context) => PostEditorThumbnailDialog(
controller: this,
),
);
}
void toggleDraftMode() { void toggleDraftMode() {
isDraft.value = !isDraft.value; isDraft.value = !isDraft.value;
} }
@ -165,6 +176,7 @@ class PostEditorController extends GetxController {
visibleUsers.clear(); visibleUsers.clear();
invisibleUsers.clear(); invisibleUsers.clear();
visibility.value = 0; visibility.value = 0;
thumbnail.value = 0;
publishedAt.value = null; publishedAt.value = null;
publishedUntil.value = null; publishedUntil.value = null;
isDraft.value = false; isDraft.value = false;
@ -246,6 +258,7 @@ class PostEditorController extends GetxController {
'title': title, 'title': title,
'description': description, 'description': description,
'content': contentController.text, 'content': contentController.text,
'thumbnail': thumbnail.value,
'tags': tags.map((x) => {'alias': x}).toList(), 'tags': tags.map((x) => {'alias': x}).toList(),
'attachments': attachments, 'attachments': attachments,
'visible_users': visibleUsers, 'visible_users': visibleUsers,
@ -269,6 +282,7 @@ class PostEditorController extends GetxController {
contentController.text = value['content'] ?? ''; contentController.text = value['content'] ?? '';
attachments.value = value['attachments'].cast<int>() ?? List.empty(); attachments.value = value['attachments'].cast<int>() ?? List.empty();
attachments.refresh(); attachments.refresh();
thumbnail.value = value['thumbnail'];
visibility.value = value['visibility']; visibility.value = value['visibility'];
isDraft.value = value['is_draft']; isDraft.value = value['is_draft'];
if (value['visible_users'] != null) { if (value['visible_users'] != null) {
@ -308,7 +322,8 @@ class PostEditorController extends GetxController {
descriptionController.text.isNotEmpty, descriptionController.text.isNotEmpty,
contentController.text.isNotEmpty, contentController.text.isNotEmpty,
attachments.isNotEmpty, attachments.isNotEmpty,
tags.isNotEmpty tags.isNotEmpty,
thumbnail.value != null,
].any((x) => x); ].any((x) => x);
} }

View File

@ -208,7 +208,12 @@ class PostCreatePopup extends StatelessWidget {
children: [ children: [
x.icon, x.icon,
const SizedBox(height: 8), const SizedBox(height: 8),
Text(x.label), Expanded(
child: Text(
x.label,
overflow: TextOverflow.fade,
),
),
], ],
).paddingAll(18), ).paddingAll(18),
), ),

View File

@ -453,6 +453,23 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
_editorController.editPublishZone(context); _editorController.editPublishZone(context);
}, },
), ),
IconButton(
icon: Obx(() {
return badges.Badge(
showBadge:
_editorController.thumbnail.value != null,
position: badges.BadgePosition.topEnd(
top: -4,
end: -6,
),
child: const Icon(Icons.preview),
);
}),
color: Theme.of(context).colorScheme.primary,
onPressed: () {
_editorController.editThumbnail(context);
},
),
IconButton( IconButton(
icon: Obx(() { icon: Obx(() {
return badges.Badge( return badges.Badge(
@ -475,14 +492,15 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
MarkdownToolbar( MarkdownToolbar(
hideImage: true, hideImage: true,
useIncludedTextField: false, useIncludedTextField: false,
backgroundColor: Colors.transparent, backgroundColor:
Theme.of(context).colorScheme.surface,
iconColor: Theme.of(context).colorScheme.onSurface, iconColor: Theme.of(context).colorScheme.onSurface,
controller: _editorController.contentController, controller: _editorController.contentController,
focusNode: _contentFocusNode, focusNode: _contentFocusNode,
borderRadius: borderRadius:
const BorderRadius.all(Radius.circular(20)), const BorderRadius.all(Radius.circular(20)),
width: 40, width: 40,
).paddingSymmetric(horizontal: 4), ).paddingSymmetric(horizontal: 2),
], ],
).paddingSymmetric(horizontal: 6, vertical: 8), ).paddingSymmetric(horizontal: 6, vertical: 8),
), ),

View File

@ -119,6 +119,9 @@ const i18nEnglish = {
'postVisibleUsers': 'Visible users', 'postVisibleUsers': 'Visible users',
'postInvisibleUsers': 'Invisible users', 'postInvisibleUsers': 'Invisible users',
'postOverview': 'Overview', 'postOverview': 'Overview',
'postThumbnail': 'Thumbnail',
'postThumbnailAttachmentNew': 'Upload thumbnail',
'postThumbnailAttachment': 'Attachment serial number',
'postPinned': 'Pinned', 'postPinned': 'Pinned',
'postListNews': 'News', 'postListNews': 'News',
'postListShuffle': 'Random', 'postListShuffle': 'Random',
@ -351,7 +354,7 @@ const i18nEnglish = {
'attachmentSaved': 'Attachment saved to your system album.', 'attachmentSaved': 'Attachment saved to your system album.',
'cropImage': 'Crop Image', 'cropImage': 'Crop Image',
'stickerUploader': 'Upload sticker', 'stickerUploader': 'Upload sticker',
'stickerUploaderAttachmentNew': 'Upload new attachment', 'stickerUploaderAttachmentNew': 'Upload sticker',
'stickerUploaderAttachment': 'Attachment serial number', 'stickerUploaderAttachment': 'Attachment serial number',
'stickerUploaderPack': 'Sticker pack serial number', 'stickerUploaderPack': 'Sticker pack serial number',
'stickerUploaderPackHint': 'stickerUploaderPackHint':

View File

@ -113,6 +113,9 @@ const i18nSimplifiedChinese = {
'postVisibleUsers': '可见帖子者', 'postVisibleUsers': '可见帖子者',
'postInvisibleUsers': '隐藏帖子者', 'postInvisibleUsers': '隐藏帖子者',
'postOverview': '帖子概览', 'postOverview': '帖子概览',
'postThumbnail': '帖子缩略图',
'postThumbnailAttachmentNew': '上传附件作为缩略图',
'postThumbnailAttachment': '附件序列号',
'postPinned': '已置顶', 'postPinned': '已置顶',
'postEditorModeStory': '发个帖子', 'postEditorModeStory': '发个帖子',
'postEditorModeArticle': '撰写文章', 'postEditorModeArticle': '撰写文章',
@ -322,7 +325,7 @@ const i18nSimplifiedChinese = {
'attachmentSaved': '附件已保存到系统相册', 'attachmentSaved': '附件已保存到系统相册',
'cropImage': '裁剪图片', 'cropImage': '裁剪图片',
'stickerUploader': '上传贴图', 'stickerUploader': '上传贴图',
'stickerUploaderAttachmentNew': '上传附件', 'stickerUploaderAttachmentNew': '上传附件作为贴图',
'stickerUploaderAttachment': '附件序列号', 'stickerUploaderAttachment': '附件序列号',
'stickerUploaderPack': '贴图包序号', 'stickerUploaderPack': '贴图包序号',
'stickerUploaderPackHint': '没有该序号?请转到我们的创作者平台创建一个贴图包。', 'stickerUploaderPackHint': '没有该序号?请转到我们的创作者平台创建一个贴图包。',

View File

@ -0,0 +1,96 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:solian/controllers/post_editor_controller.dart';
import 'package:solian/widgets/attachments/attachment_editor.dart';
class PostEditorThumbnailDialog extends StatefulWidget {
final PostEditorController controller;
const PostEditorThumbnailDialog({super.key, required this.controller});
@override
State<PostEditorThumbnailDialog> createState() =>
_PostEditorThumbnailDialogState();
}
class _PostEditorThumbnailDialogState extends State<PostEditorThumbnailDialog> {
final TextEditingController _attachmentController = TextEditingController();
void _promptUploadNewAttachment() {
showModalBottomSheet(
context: context,
builder: (context) => AttachmentEditorPopup(
usage: 'i.attachment',
singleMode: true,
imageOnly: true,
autoUpload: true,
onAdd: (value) {
setState(() {
_attachmentController.text = value.toString();
});
widget.controller.thumbnail.value = value;
},
initialAttachments: const [],
onRemove: (_) {},
),
);
}
@override
void initState() {
_attachmentController.text =
widget.controller.thumbnail.value?.toString() ?? '';
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: [
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)),
),
onTap: () {
_promptUploadNewAttachment();
},
),
const SizedBox(height: 8),
TextField(
controller: _attachmentController,
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
prefixText: '#',
labelText: 'postThumbnailAttachment'.tr,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
],
),
actions: [
TextButton(
onPressed: () {
widget.controller.thumbnail.value =
int.tryParse(_attachmentController.text);
Navigator.pop(context);
},
child: Text('confirm'.tr),
),
],
);
}
}

View File

@ -76,6 +76,30 @@ class _PostItemState extends State<PostItem> {
} }
} }
Widget _buildThumbnail() {
if (widget.item.body['thumbnail'] == null) return const SizedBox();
const radius = BorderRadius.all(Radius.circular(8));
return AspectRatio(
aspectRatio: 16 / 9,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).dividerColor,
width: 0.3,
),
borderRadius: radius,
),
child: ClipRRect(
borderRadius: radius,
child: AttachmentSelfContainedEntry(
id: widget.item.body['thumbnail'],
parentId: 'p${item.id}-thumbnail',
),
),
),
);
}
Widget _buildHeader() { Widget _buildHeader() {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -283,6 +307,7 @@ class _PostItemState extends State<PostItem> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildThumbnail().paddingSymmetric(horizontal: 12, vertical: 4),
_buildHeader().paddingSymmetric(horizontal: 12), _buildHeader().paddingSymmetric(horizontal: 12),
_buildHeaderDivider().paddingSymmetric(horizontal: 12), _buildHeaderDivider().paddingSymmetric(horizontal: 12),
Stack( Stack(
@ -356,6 +381,7 @@ class _PostItemState extends State<PostItem> {
closedBuilder: (_, openContainer) => Column( closedBuilder: (_, openContainer) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildThumbnail().paddingSymmetric(horizontal: 12, vertical: 4),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -390,7 +416,7 @@ class _PostItemState extends State<PostItem> {
setState(() => _contentHeight = size.height); setState(() => _contentHeight = size.height);
}, },
child: MarkdownTextContent( child: MarkdownTextContent(
parentId: 'p${item.id}', parentId: 'p${item.id}-embed',
content: item.body['content'], content: item.body['content'],
isSelectable: widget.isContentSelectable, isSelectable: widget.isContentSelectable,
).paddingOnly(left: 12, right: 8), ).paddingOnly(left: 12, right: 8),