✨ Articles
This commit is contained in:
@ -7,7 +7,7 @@ import 'package:solian/exts.dart';
|
||||
import 'package:solian/models/articles.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/screens/articles/article_publish.dart';
|
||||
import 'package:solian/screens/articles/article_editor.dart';
|
||||
|
||||
class ArticleAction extends StatefulWidget {
|
||||
final Article item;
|
||||
@ -71,7 +71,7 @@ class _ArticleActionState extends State<ArticleAction> {
|
||||
title: Text('edit'.tr),
|
||||
onTap: () async {
|
||||
final value = await AppRouter.instance.pushNamed(
|
||||
'articleCreate',
|
||||
'articleEditor',
|
||||
extra: ArticlePublishArguments(edit: widget.item),
|
||||
);
|
||||
if (value != null) {
|
||||
|
@ -5,7 +5,6 @@ import 'package:solian/models/articles.dart';
|
||||
import 'package:solian/widgets/account/account_avatar.dart';
|
||||
import 'package:solian/widgets/account/account_profile_popup.dart';
|
||||
import 'package:solian/widgets/articles/article_quick_action.dart';
|
||||
import 'package:solian/widgets/attachments/attachment_list.dart';
|
||||
import 'package:solian/widgets/feed/feed_content.dart';
|
||||
import 'package:solian/widgets/feed/feed_tags.dart';
|
||||
import 'package:timeago/timeago.dart' show format;
|
||||
@ -55,14 +54,14 @@ class _ArticleItemState extends State<ArticleItem> {
|
||||
Text(
|
||||
item.author.nick,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
).paddingOnly(left: 12),
|
||||
),
|
||||
buildDate().paddingOnly(left: 4),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildFooter() {
|
||||
List<String> labels = List.empty(growable: true);
|
||||
List<String> labels = List.from(['article'.tr], growable: true);
|
||||
if (widget.item.createdAt != widget.item.updatedAt) {
|
||||
labels.add('postEdited'.trParams({
|
||||
'date': DateFormat('yy/M/d H:m').format(item.updatedAt.toLocal()),
|
||||
@ -103,10 +102,36 @@ class _ArticleItemState extends State<ArticleItem> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!widget.isFullContent) {
|
||||
return ListTile(
|
||||
leading: AccountAvatar(content: item.author.avatar.toString()),
|
||||
title: Text(item.title),
|
||||
subtitle: Text(item.description),
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AccountAvatar(content: item.author.avatar.toString()),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
buildHeader(),
|
||||
Text(item.title, style: const TextStyle(fontSize: 15)),
|
||||
Text(
|
||||
item.description,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.8),
|
||||
),
|
||||
),
|
||||
buildFooter(),
|
||||
],
|
||||
).paddingOnly(left: 12),
|
||||
),
|
||||
],
|
||||
).paddingOnly(
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
right: 16,
|
||||
left: 16,
|
||||
);
|
||||
}
|
||||
|
||||
@ -135,13 +160,18 @@ class _ArticleItemState extends State<ArticleItem> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
buildHeader(),
|
||||
FeedContent(content: item.content).paddingOnly(
|
||||
left: 12,
|
||||
right: 8,
|
||||
Text(item.title),
|
||||
Text(
|
||||
item.description,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.8),
|
||||
),
|
||||
),
|
||||
buildFooter().paddingOnly(left: 12),
|
||||
],
|
||||
),
|
||||
).paddingOnly(left: 12),
|
||||
)
|
||||
],
|
||||
).paddingOnly(
|
||||
@ -149,11 +179,17 @@ class _ArticleItemState extends State<ArticleItem> {
|
||||
right: 16,
|
||||
left: 16,
|
||||
),
|
||||
AttachmentList(
|
||||
parentId: widget.item.alias,
|
||||
attachmentsId: item.attachments ?? List.empty(),
|
||||
divided: true,
|
||||
const Divider(thickness: 0.3, height: 0.3).paddingSymmetric(
|
||||
vertical: 10,
|
||||
),
|
||||
FeedContent(content: item.content).paddingSymmetric(
|
||||
horizontal: 20,
|
||||
),
|
||||
const Divider(thickness: 0.3, height: 0.3).paddingOnly(
|
||||
top: 10,
|
||||
bottom: 6,
|
||||
),
|
||||
buildFooter().paddingOnly(left: 20),
|
||||
if (widget.isReactable)
|
||||
ArticleQuickAction(
|
||||
isReactable: widget.isReactable,
|
||||
@ -166,7 +202,7 @@ class _ArticleItemState extends State<ArticleItem> {
|
||||
},
|
||||
).paddingOnly(
|
||||
top: 6,
|
||||
left: 60,
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 10,
|
||||
)
|
||||
|
85
lib/widgets/articles/article_list.dart
Normal file
85
lib/widgets/articles/article_list.dart
Normal file
@ -0,0 +1,85 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:solian/models/articles.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/widgets/articles/article_action.dart';
|
||||
import 'package:solian/widgets/articles/article_item.dart';
|
||||
import 'package:solian/widgets/centered_container.dart';
|
||||
|
||||
class ArticleListWidget extends StatelessWidget {
|
||||
final bool isShowEmbed;
|
||||
final bool isClickable;
|
||||
final bool isNestedClickable;
|
||||
final PagingController<int, Article> controller;
|
||||
|
||||
const ArticleListWidget({
|
||||
super.key,
|
||||
required this.controller,
|
||||
this.isShowEmbed = true,
|
||||
this.isClickable = true,
|
||||
this.isNestedClickable = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PagedSliverList<int, Article>.separated(
|
||||
pagingController: controller,
|
||||
builderDelegate: PagedChildBuilderDelegate<Article>(
|
||||
itemBuilder: (context, item, index) {
|
||||
return RepaintBoundary(
|
||||
child: CenteredContainer(
|
||||
child: ArticleListEntryWidget(
|
||||
isClickable: isClickable,
|
||||
item: item,
|
||||
onUpdate: () {
|
||||
controller.refresh();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
separatorBuilder: (_, __) => const Divider(thickness: 0.3, height: 0.3),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ArticleListEntryWidget extends StatelessWidget {
|
||||
final bool isClickable;
|
||||
final Article item;
|
||||
final Function onUpdate;
|
||||
|
||||
const ArticleListEntryWidget({
|
||||
super.key,
|
||||
required this.isClickable,
|
||||
required this.item,
|
||||
required this.onUpdate,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
child: ArticleItem(
|
||||
key: Key('a${item.alias}'),
|
||||
item: item,
|
||||
).paddingSymmetric(vertical: 8),
|
||||
onTap: () {
|
||||
if (!isClickable) return;
|
||||
AppRouter.instance.pushNamed(
|
||||
'articleDetail',
|
||||
pathParameters: {'alias': item.alias},
|
||||
);
|
||||
},
|
||||
onLongPress: () {
|
||||
showModalBottomSheet(
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder: (context) => ArticleAction(item: item),
|
||||
).then((value) {
|
||||
if (value != null) onUpdate();
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -50,7 +50,7 @@ class _ArticleQuickActionState extends State<ArticleQuickAction> {
|
||||
|
||||
setState(() => _isSubmitting = true);
|
||||
|
||||
final resp = await client.post('/api/posts/${widget.item.alias}/react', {
|
||||
final resp = await client.post('/api/articles/${widget.item.alias}/react', {
|
||||
'symbol': symbol,
|
||||
'attitude': attitude,
|
||||
});
|
||||
|
Reference in New Issue
Block a user