✨ 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