✨ Thumbnail
This commit is contained in:
parent
4a3e6a9e15
commit
22026efa7d
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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),
|
||||||
),
|
),
|
||||||
|
@ -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),
|
||||||
),
|
),
|
||||||
|
@ -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':
|
||||||
|
@ -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': '没有该序号?请转到我们的创作者平台创建一个贴图包。',
|
||||||
|
96
lib/widgets/posts/editor/post_editor_thumbnail.dart
Normal file
96
lib/widgets/posts/editor/post_editor_thumbnail.dart
Normal 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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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),
|
||||||
|
Loading…
Reference in New Issue
Block a user