Articles writing

This commit is contained in:
LittleSheep 2024-07-09 23:06:55 +08:00
parent fa600d6c69
commit 065cda27e9
5 changed files with 176 additions and 168 deletions

View File

@ -12,6 +12,7 @@ import 'package:solian/widgets/attachments/attachment_publish.dart';
import 'package:solian/widgets/feed/feed_tags_field.dart';
import 'package:solian/widgets/prev_page.dart';
import 'package:textfield_tags/textfield_tags.dart';
import 'package:badges/badges.dart' as badges;
class ArticlePublishArguments {
final Post? edit;
@ -44,6 +45,8 @@ class _ArticlePublishScreenState extends State<ArticlePublishScreen> {
List<int> _attachments = List.empty();
bool _isDraft = false;
void showAttachments() {
showModalBottomSheet(
context: context,
@ -51,7 +54,9 @@ class _ArticlePublishScreenState extends State<ArticlePublishScreen> {
builder: (context) => AttachmentPublishPopup(
usage: 'i.attachment',
current: _attachments,
onUpdate: (value) => _attachments = value,
onUpdate: (value) {
setState(() => _attachments = value);
},
),
);
}
@ -72,6 +77,7 @@ class _ArticlePublishScreenState extends State<ArticlePublishScreen> {
'tags': _tagsController.getTags?.map((x) => {'alias': x}).toList() ??
List.empty(),
'attachments': _attachments,
'is_draft': _isDraft,
if (widget.edit != null) 'alias': widget.edit!.alias,
if (widget.realm != null) 'realm': widget.realm!.alias,
};
@ -95,6 +101,7 @@ class _ArticlePublishScreenState extends State<ArticlePublishScreen> {
if (widget.edit != null) {
_contentController.text = widget.edit!.content;
_attachments = widget.edit!.attachments ?? List.empty();
_isDraft = widget.edit!.isDraft ?? false;
}
}
@ -128,81 +135,102 @@ class _ArticlePublishScreenState extends State<ArticlePublishScreen> {
actions: [
TextButton(
onPressed: _isBusy ? null : () => applyPost(),
child: Text('postAction'.tr.toUpperCase()),
child: Text(
_isDraft
? 'draftSave'.tr.toUpperCase()
: 'postAction'.tr.toUpperCase(),
),
)
],
),
body: SafeArea(
top: false,
child: Stack(
children: [
ListView(
children: [
if (_isBusy)
const LinearProgressIndicator().animate().scaleX(),
if (widget.edit != null)
MaterialBanner(
leading: const Icon(Icons.edit),
leadingPadding:
const EdgeInsets.only(left: 10, right: 20),
dividerColor: Colors.transparent,
content: Text('postEditingNotify'.tr),
actions: notifyBannerActions,
),
if (widget.realm != null)
MaterialBanner(
leading: const Icon(Icons.group),
leadingPadding:
const EdgeInsets.only(left: 10, right: 20),
dividerColor: Colors.transparent,
content: Text(
'postInRealmNotify'
.trParams({'realm': '#${widget.realm!.alias}'}),
),
actions: notifyBannerActions,
),
const Divider(thickness: 0.3, height: 0.3)
.paddingOnly(bottom: 6),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: TextField(
maxLines: null,
autofocus: true,
autocorrect: true,
keyboardType: TextInputType.multiline,
controller: _titleController,
decoration: InputDecoration.collapsed(
hintText: 'articleTitlePlaceholder'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
body: Stack(
children: [
ListView(
children: [
if (_isBusy) const LinearProgressIndicator().animate().scaleX(),
if (widget.edit != null)
MaterialBanner(
leading: const Icon(Icons.edit),
leadingPadding: const EdgeInsets.only(left: 10, right: 20),
dividerColor: Colors.transparent,
content: Text('postEditingNotify'.tr),
actions: notifyBannerActions,
),
const Divider(thickness: 0.3, height: 0.3)
.paddingSymmetric(vertical: 6),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: TextField(
maxLines: null,
autofocus: true,
autocorrect: true,
keyboardType: TextInputType.multiline,
controller: _contentController,
decoration: InputDecoration.collapsed(
hintText: 'articleContentPlaceholder'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
if (widget.realm != null)
MaterialBanner(
leading: const Icon(Icons.group),
leadingPadding: const EdgeInsets.only(left: 10, right: 20),
dividerColor: Colors.transparent,
content: Text(
'postInRealmNotify'
.trParams({'realm': '#${widget.realm!.alias}'}),
),
actions: notifyBannerActions,
),
],
),
Positioned(
bottom: 0,
left: 0,
right: 0,
const Divider(thickness: 0.3, height: 0.3)
.paddingOnly(bottom: 6),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: TextField(
maxLines: null,
autofocus: true,
autocorrect: true,
controller: _titleController,
decoration: InputDecoration.collapsed(
hintText: 'articleTitlePlaceholder'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
),
const Divider(thickness: 0.3, height: 0.3)
.paddingOnly(bottom: 6),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: TextField(
minLines: 1,
maxLines: 3,
autofocus: true,
autocorrect: true,
keyboardType: TextInputType.multiline,
controller: _descriptionController,
decoration: InputDecoration.collapsed(
hintText: 'articleDescriptionPlaceholder'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
),
const Divider(thickness: 0.3, height: 0.3)
.paddingSymmetric(vertical: 6),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: TextField(
maxLines: null,
autofocus: true,
autocorrect: true,
keyboardType: TextInputType.multiline,
controller: _contentController,
decoration: InputDecoration.collapsed(
hintText: 'articleContentPlaceholder'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
),
const SizedBox(height: 120),
],
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Material(
elevation: 8,
color: Theme.of(context).colorScheme.surface,
child: Column(
children: [
TagsField(
@ -214,23 +242,44 @@ class _ArticlePublishScreenState extends State<ArticlePublishScreen> {
const Divider(thickness: 0.3, height: 0.3),
SizedBox(
height: 56,
child: Row(
child: ListView(
scrollDirection: Axis.horizontal,
children: [
TextButton(
style: TextButton.styleFrom(
shape: const CircleBorder(),
IconButton(
icon: _isDraft
? const Icon(Icons.drive_file_rename_outline)
: const Icon(Icons.public),
color: _isDraft
? Colors.grey.shade600
: Colors.green.shade700,
onPressed: () {
setState(() => _isDraft = !_isDraft);
},
),
IconButton(
icon: badges.Badge(
badgeContent: Text(
_attachments.length.toString(),
style: const TextStyle(color: Colors.white),
),
showBadge: _attachments.isNotEmpty,
position: badges.BadgePosition.topEnd(
top: -12,
end: -8,
),
child: const Icon(Icons.camera_alt),
),
child: const Icon(Icons.camera_alt),
color: Theme.of(context).colorScheme.primary,
onPressed: () => showAttachments(),
)
),
],
),
).paddingSymmetric(horizontal: 6, vertical: 8),
),
],
),
).paddingOnly(bottom: MediaQuery.of(context).padding.bottom),
),
],
),
),
],
),
),
);

View File

@ -60,7 +60,9 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
builder: (context) => AttachmentPublishPopup(
usage: 'i.attachment',
current: _attachments,
onUpdate: (value) => _attachments = value,
onUpdate: (value) {
setState(() => _attachments = value);
},
),
);
}
@ -261,14 +263,22 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
setState(() => _isDraft = !_isDraft);
},
),
badges.Badge(
badgeContent: Text(_attachments.length.toString()),
showBadge: _attachments.isNotEmpty,
child: IconButton(
icon: const Icon(Icons.camera_alt),
onPressed: () => showAttachments(),
IconButton(
icon: badges.Badge(
badgeContent: Text(
_attachments.length.toString(),
style: const TextStyle(color: Colors.white),
),
showBadge: _attachments.isNotEmpty,
position: badges.BadgePosition.topEnd(
top: -12,
end: -8,
),
child: const Icon(Icons.camera_alt),
),
)
color: Theme.of(context).colorScheme.primary,
onPressed: () => showAttachments(),
),
],
).paddingSymmetric(horizontal: 6, vertical: 8),
),

View File

@ -91,6 +91,7 @@ const messagesEnglish = {
'postPublish': 'Post a post',
'articlePublish': 'Write an article',
'articleTitlePlaceholder': 'Title',
'articleDescriptionPlaceholder': 'Description',
'articleContentPlaceholder': 'Content',
'postIdentityNotify': 'You will post this post as',
'postContentPlaceholder': 'What\'s happened?!',

View File

@ -275,22 +275,25 @@ class _PostItemState extends State<PostItem> {
attachmentsId: item.attachments ?? List.empty(),
divided: true,
),
PostQuickAction(
isShowReply: widget.isShowReply,
isReactable: widget.isReactable,
item: widget.item,
onReact: (symbol, changes) {
setState(() {
item.reactionList[symbol] =
(item.reactionList[symbol] ?? 0) + changes;
});
},
).paddingOnly(
top: hasAttachment ? 10 : 6,
left: hasAttachment ? 24 : 60,
right: 16,
bottom: 10,
),
if (!widget.isShowReply && widget.isReactable)
PostQuickAction(
isShowReply: widget.isShowReply,
isReactable: widget.isReactable,
item: widget.item,
onReact: (symbol, changes) {
setState(() {
item.reactionList[symbol] =
(item.reactionList[symbol] ?? 0) + changes;
});
},
).paddingOnly(
top: hasAttachment ? 10 : 6,
left: hasAttachment ? 24 : 60,
right: 16,
bottom: 10,
)
else
const SizedBox(height: 10),
],
);
}

View File

@ -1,9 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:solian/models/post.dart';
import 'package:solian/widgets/feed/feed_content.dart';
import 'package:solian/widgets/feed/feed_tags.dart';
import 'package:solian/widgets/posts/post_item.dart';
class PostOwnedListEntry extends StatelessWidget {
final Post item;
@ -15,61 +13,6 @@ class PostOwnedListEntry extends StatelessWidget {
required this.onTap,
});
Widget buildFooter(BuildContext context) {
List<String> labels = List.empty(growable: true);
if (item.createdAt == item.updatedAt) {
labels.add('postNewCreated'.trParams({
'date': DateFormat('yyyy/MM/dd HH:mm').format(item.updatedAt.toLocal()),
}));
} else {
labels.add('postEdited'.trParams({
'date': DateFormat('yyyy/MM/dd HH:mm').format(item.updatedAt.toLocal()),
}));
}
if (item.realm != null) {
labels.add('postInRealm'.trParams({
'realm': '#${item.realm!.alias}',
}));
}
final color = Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
List<Widget> widgets = List.from([
Row(
children: [
Text(
'post'.tr,
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 12,
color: color,
),
),
Icon(Icons.text_snippet, size: 14, color: color).paddingOnly(left: 4),
],
),
], growable: true);
if (item.tags?.isNotEmpty ?? false) {
widgets.add(FeedTagsList(tags: item.tags!));
}
if (labels.isNotEmpty) {
widgets.add(Text(
labels.join(' · '),
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 12,
color: color,
),
));
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: widgets,
);
}
@override
Widget build(BuildContext context) {
return Card(
@ -78,12 +21,14 @@ class PostOwnedListEntry extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FeedContent(content: item.content).paddingOnly(
left: 12,
right: 12,
top: 8,
),
buildFooter(context).paddingOnly(left: 12, top: 6, bottom: 8),
PostItem(
key: Key('p${item.alias}'),
item: item,
isShowEmbed: false,
isClickable: false,
isShowReply: false,
isReactable: false,
).paddingSymmetric(vertical: 8),
],
),
onTap: () => onTap(),