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

View File

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

View File

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

View File

@ -1,9 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:solian/models/post.dart'; import 'package:solian/models/post.dart';
import 'package:solian/widgets/feed/feed_content.dart'; import 'package:solian/widgets/posts/post_item.dart';
import 'package:solian/widgets/feed/feed_tags.dart';
class PostOwnedListEntry extends StatelessWidget { class PostOwnedListEntry extends StatelessWidget {
final Post item; final Post item;
@ -15,61 +13,6 @@ class PostOwnedListEntry extends StatelessWidget {
required this.onTap, 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return Card(
@ -78,12 +21,14 @@ class PostOwnedListEntry extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
FeedContent(content: item.content).paddingOnly( PostItem(
left: 12, key: Key('p${item.alias}'),
right: 12, item: item,
top: 8, isShowEmbed: false,
), isClickable: false,
buildFooter(context).paddingOnly(left: 12, top: 6, bottom: 8), isShowReply: false,
isReactable: false,
).paddingSymmetric(vertical: 8),
], ],
), ),
onTap: () => onTap(), onTap: () => onTap(),