diff --git a/lib/router.dart b/lib/router.dart index 28c3131..3ee6358 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -1,6 +1,7 @@ import 'package:go_router/go_router.dart'; import 'package:solian/screens/account.dart'; import 'package:solian/screens/explore.dart'; +import 'package:solian/screens/posts/new_moment.dart'; import 'package:solian/screens/posts/screen.dart'; final router = GoRouter( @@ -15,6 +16,11 @@ final router = GoRouter( name: 'account', builder: (context, state) => const AccountScreen(), ), + GoRoute( + path: '/posts/moments/new', + name: 'posts.moments.new', + builder: (context, state) => const NewMomentScreen(), + ), GoRoute( path: '/posts/:dataset/:alias', name: 'posts.screen', diff --git a/lib/screens/posts/new_moment.dart b/lib/screens/posts/new_moment.dart new file mode 100644 index 0000000..9475789 --- /dev/null +++ b/lib/screens/posts/new_moment.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class NewMomentScreen extends StatelessWidget { + const NewMomentScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 640), + child: Column( + children: [ + + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/posts/comment_list.dart b/lib/widgets/posts/comment_list.dart index 6ef0cef..e4f773a 100644 --- a/lib/widgets/posts/comment_list.dart +++ b/lib/widgets/posts/comment_list.dart @@ -89,7 +89,7 @@ class CommentListHeader extends StatelessWidget { final auth = context.read(); return Container( - padding: const EdgeInsets.only(left: 10, right: 10, top: 20), + padding: const EdgeInsets.only(left: 8, right: 8, top: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/widgets/posts/content/article.dart b/lib/widgets/posts/content/article.dart index 6ed81c0..2930b93 100644 --- a/lib/widgets/posts/content/article.dart +++ b/lib/widgets/posts/content/article.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:solian/models/post.dart'; import 'package:markdown/markdown.dart' as markdown; +import 'package:solian/utils/service_url.dart'; import 'package:url_launcher/url_launcher_string.dart'; class ArticleContent extends StatelessWidget { @@ -12,31 +13,30 @@ class ArticleContent extends StatelessWidget { @override Widget build(BuildContext context) { - return brief - ? Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - item.title, - style: Theme.of(context).textTheme.titleMedium, - ), - Text( - item.description, - style: Theme.of(context).textTheme.bodyMedium, - ) - ], - ), + final headingPart = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.title, + style: Theme.of(context).textTheme.titleMedium, + ), + Text( + item.description, + style: Theme.of(context).textTheme.bodyMedium, ) + ], + ); + + return brief + ? headingPart : Column( children: [ - ListTile( - title: Text(item.title), - subtitle: Text(item.description), + Padding( + padding: const EdgeInsets.only(bottom: 12), + child: headingPart, ), - const Divider(color: Color(0xffefefef)), Markdown( + padding: const EdgeInsets.all(0), selectable: !brief, data: item.content, shrinkWrap: true, @@ -52,6 +52,15 @@ class ArticleContent extends StatelessWidget { mode: LaunchMode.externalApplication, ); }, + imageBuilder: (url, _, __) { + if (url.toString().startsWith("/api/attachments")) { + return Image.network( + getRequestUri('interactive', url.toString()) + .toString()); + } else { + return Image.network(url.toString()); + } + }, ), ], ); diff --git a/lib/widgets/posts/content/attachment.dart b/lib/widgets/posts/content/attachment.dart index 6b62c76..e7234f9 100644 --- a/lib/widgets/posts/content/attachment.dart +++ b/lib/widgets/posts/content/attachment.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:chewie/chewie.dart'; import 'package:solian/models/post.dart'; @@ -8,8 +9,9 @@ import 'package:video_player/video_player.dart'; class AttachmentItem extends StatefulWidget { final Attachment item; + final String? badge; - const AttachmentItem({super.key, required this.item}); + const AttachmentItem({super.key, required this.item, this.badge}); @override State createState() => _AttachmentItemState(); @@ -36,9 +38,22 @@ class _AttachmentItemState extends State { borderRadius: const BorderRadius.all(borderRadius), child: Hero( tag: getTag(), - child: Image.network( - getFileUri().toString(), - fit: BoxFit.cover, + child: Stack( + children: [ + Image.network( + getFileUri().toString(), + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, + ), + widget.badge == null + ? Container() + : Positioned( + right: 12, + bottom: 8, + child: Chip(label: Text(widget.badge!)), + ) + ], ), ), ), @@ -110,18 +125,21 @@ class AttachmentList extends StatelessWidget { @override Widget build(BuildContext context) { + var renderProgress = 0; return FlutterCarousel( options: CarouselOptions( aspectRatio: 16 / 9, - showIndicator: true, - slideIndicator: const CircularSlideIndicator(), + viewportFraction: 1, ), items: items.map((item) { + renderProgress++; + final badge = '$renderProgress/${items.length}'; return Builder( builder: (BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 4), - child: AttachmentItem(item: item), + child: AttachmentItem( + item: item, badge: items.length <= 1 ? null : badge), ); }, ); diff --git a/lib/widgets/posts/content/moment.dart b/lib/widgets/posts/content/moment.dart index 816c928..386dc48 100644 --- a/lib/widgets/posts/content/moment.dart +++ b/lib/widgets/posts/content/moment.dart @@ -15,7 +15,7 @@ class MomentContent extends StatelessWidget { data: item.content, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.symmetric(horizontal: 12), + padding: const EdgeInsets.all(0), ); } } diff --git a/lib/widgets/posts/item.dart b/lib/widgets/posts/item.dart index 2f1b553..816b21b 100644 --- a/lib/widgets/posts/item.dart +++ b/lib/widgets/posts/item.dart @@ -20,7 +20,7 @@ class _PostItemState extends State { Widget renderContent() { switch (widget.item.modelType) { - case "article": + case 'article': return ArticleContent(item: widget.item, brief: widget.brief ?? true); default: return MomentContent(item: widget.item, brief: widget.brief ?? true); @@ -28,56 +28,110 @@ class _PostItemState extends State { } Widget renderAttachments() { - if(widget.item.attachments != null && widget.item.attachments!.isNotEmpty) { - return AttachmentList(items: widget.item.attachments!); + if (widget.item.modelType == 'article') return Container(); + + if (widget.item.attachments != null && + widget.item.attachments!.isNotEmpty) { + return Padding( + padding: const EdgeInsets.only(top: 8), + child: AttachmentList(items: widget.item.attachments!), + ); } else { return Container(); } } + String getAuthorDescribe() => widget.item.author.description.isNotEmpty + ? widget.item.author.description + : 'No description yet.'; + @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CircleAvatar( - backgroundImage: NetworkImage(widget.item.author.avatar), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Row( - children: [ - Text( - widget.item.author.nick, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(width: 4), - Text( - timeago.format(widget.item.createdAt) - ) - ], - ), - ), - renderContent(), - ], + final headingParts = [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Row( + children: [ + Text( + widget.item.author.nick, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(width: 4), + Text(timeago.format(widget.item.createdAt)) + ], + ), + ), + ]; + + if (widget.brief ?? true) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CircleAvatar( + backgroundImage: NetworkImage(widget.item.author.avatar), ), - ), - ], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...headingParts, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: renderContent(), + ), + renderAttachments(), + ], + ), + ), + ], + ), + ], + ), + ); + } else { + return Column( + children: [ + Padding( + padding: const EdgeInsets.only(left: 12, right: 12, top: 16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CircleAvatar( + backgroundImage: NetworkImage(widget.item.author.avatar), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...headingParts, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + getAuthorDescribe(), + maxLines: 1, + ), + ), + ], + ), + ), + ], + ), + ), + const Padding( + padding: EdgeInsets.only(top: 6), + child: Divider(thickness: 0.3), ), Padding( - padding: const EdgeInsets.all(8.0), - child: renderAttachments(), + padding: const EdgeInsets.symmetric(horizontal: 16), + child: renderContent(), ), + renderAttachments() ], - ), - ); + ); + } } }