✨ Articles writing
This commit is contained in:
parent
fa600d6c69
commit
065cda27e9
@ -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),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -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),
|
||||||
),
|
),
|
||||||
|
@ -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?!',
|
||||||
|
@ -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),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
|
Loading…
Reference in New Issue
Block a user