💄 Optimized post list
This commit is contained in:
parent
a04bfe4cf9
commit
32c33a963a
@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
@ -87,28 +88,70 @@ class _ExploreScreenState extends State<ExploreScreen>
|
||||
|
||||
final scrollProgress =
|
||||
(scrollOffset / colorChangeOffset).clamp(0.0, 1.0);
|
||||
final backgroundColor = Color.lerp(
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceContainerLow
|
||||
.withOpacity(0),
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceContainerLow
|
||||
.withOpacity(0.9),
|
||||
scrollProgress,
|
||||
);
|
||||
final blurSigma = lerpDouble(0, 10, scrollProgress) ?? 0;
|
||||
|
||||
return SliverAppBar(
|
||||
backgroundColor: backgroundColor,
|
||||
flexibleSpace: SizedBox(
|
||||
height: 48,
|
||||
child: const Row(
|
||||
children: [
|
||||
RealmSwitcher(),
|
||||
],
|
||||
).paddingSymmetric(horizontal: 8),
|
||||
).paddingOnly(top: MediaQuery.of(context).padding.top),
|
||||
flexibleSpace: ClipRRect(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: blurSigma,
|
||||
sigmaY: blurSigma,
|
||||
),
|
||||
child: ListView(
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: const Row(
|
||||
children: [
|
||||
RealmSwitcher(),
|
||||
],
|
||||
).paddingSymmetric(horizontal: 8),
|
||||
),
|
||||
TabBar(
|
||||
controller: _tabController,
|
||||
dividerHeight: 0.3,
|
||||
tabAlignment: TabAlignment.fill,
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.feed, size: 20),
|
||||
const Gap(8),
|
||||
Text('postListNews'.tr),
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.people, size: 20),
|
||||
const Gap(8),
|
||||
Text('postListFriends'.tr),
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.shuffle_on_outlined,
|
||||
size: 20),
|
||||
const Gap(8),
|
||||
Text('postListShuffle'.tr),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).paddingOnly(top: MediaQuery.of(context).padding.top),
|
||||
),
|
||||
),
|
||||
expandedHeight: 96,
|
||||
snap: true,
|
||||
floating: true,
|
||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||
@ -120,43 +163,6 @@ class _ExploreScreenState extends State<ExploreScreen>
|
||||
width: AppTheme.isLargeScreen(context) ? 8 : 16,
|
||||
),
|
||||
],
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
dividerHeight: 0.3,
|
||||
tabAlignment: TabAlignment.fill,
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.feed, size: 20),
|
||||
const Gap(8),
|
||||
Text('postListNews'.tr),
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.people, size: 20),
|
||||
const Gap(8),
|
||||
Text('postListFriends'.tr),
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.shuffle_on_outlined, size: 20),
|
||||
const Gap(8),
|
||||
Text('postListShuffle'.tr),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
@ -180,6 +186,12 @@ class _ExploreScreenState extends State<ExploreScreen>
|
||||
onRefresh: () => _postController.reloadAllOver(),
|
||||
child: CustomScrollView(slivers: [
|
||||
ControlledPostListWidget(
|
||||
padding: AppTheme.isLargeScreen(context)
|
||||
? EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 8,
|
||||
)
|
||||
: EdgeInsets.zero,
|
||||
controller: _postController.pagingController,
|
||||
onUpdate: () => _postController.reloadAllOver(),
|
||||
),
|
||||
@ -191,6 +203,9 @@ class _ExploreScreenState extends State<ExploreScreen>
|
||||
onRefresh: () => _postController.reloadAllOver(),
|
||||
child: CustomScrollView(slivers: [
|
||||
ControlledPostListWidget(
|
||||
padding: AppTheme.isLargeScreen(context)
|
||||
? EdgeInsets.symmetric(horizontal: 16)
|
||||
: EdgeInsets.zero,
|
||||
controller: _postController.pagingController,
|
||||
onUpdate: () => _postController.reloadAllOver(),
|
||||
),
|
||||
|
@ -4,6 +4,7 @@ import 'package:solian/exts.dart';
|
||||
import 'package:solian/models/post.dart';
|
||||
import 'package:solian/providers/content/posts.dart';
|
||||
import 'package:solian/providers/last_read.dart';
|
||||
import 'package:solian/theme.dart';
|
||||
import 'package:solian/widgets/posts/post_item.dart';
|
||||
import 'package:solian/widgets/posts/post_replies.dart';
|
||||
|
||||
@ -67,11 +68,18 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
||||
isFullContent: true,
|
||||
isShowReply: false,
|
||||
isContentSelectable: true,
|
||||
padding: AppTheme.isLargeScreen(context)
|
||||
? EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 8,
|
||||
)
|
||||
: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child:
|
||||
const Divider(thickness: 0.3, height: 1).paddingOnly(top: 4),
|
||||
child: const Divider(thickness: 0.3, height: 1).paddingOnly(
|
||||
top: 8,
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Align(
|
||||
@ -82,7 +90,15 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
||||
).paddingOnly(left: 24, right: 24, top: 16),
|
||||
),
|
||||
),
|
||||
PostReplyList(item: item!),
|
||||
PostReplyList(
|
||||
item: item!,
|
||||
padding: AppTheme.isLargeScreen(context)
|
||||
? EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 8,
|
||||
)
|
||||
: EdgeInsets.zero,
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(height: MediaQuery.of(context).padding.bottom),
|
||||
),
|
||||
|
@ -89,8 +89,7 @@ class _AccountProfilePopupState extends State<AccountProfilePopup> {
|
||||
|
||||
return SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.75,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
child: ListView(
|
||||
children: [
|
||||
AccountHeadingWidget(
|
||||
avatar: _userinfo!.avatar,
|
||||
|
@ -155,11 +155,18 @@ class _AttachmentItemImage extends StatelessWidget {
|
||||
),
|
||||
if (showBadge && badge != null)
|
||||
Positioned(
|
||||
right: 12,
|
||||
bottom: 8,
|
||||
right: 8,
|
||||
bottom: 4,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Chip(label: Text(badge!)),
|
||||
child: Chip(
|
||||
label: Text(badge!),
|
||||
labelStyle: GoogleFonts.robotoMono(),
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -4,
|
||||
vertical: -2,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showHideButton && item.isMature)
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:carousel_slider/carousel_slider.dart';
|
||||
import 'package:dismissible_page/dismissible_page.dart';
|
||||
import 'package:flutter/material.dart' hide CarouselController;
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
@ -23,6 +22,7 @@ class AttachmentList extends StatefulWidget {
|
||||
final bool autoload;
|
||||
final double columnMaxWidth;
|
||||
|
||||
final EdgeInsets? padding;
|
||||
final double? width;
|
||||
final double? viewport;
|
||||
|
||||
@ -36,6 +36,7 @@ class AttachmentList extends StatefulWidget {
|
||||
this.isFullWidth = false,
|
||||
this.autoload = false,
|
||||
this.columnMaxWidth = 480,
|
||||
this.padding,
|
||||
this.width,
|
||||
this.viewport,
|
||||
});
|
||||
@ -161,9 +162,7 @@ class _AttachmentListState extends State<AttachmentList> {
|
||||
color: _unFocusColor,
|
||||
).paddingOnly(right: 5),
|
||||
Text(
|
||||
'attachmentHint'.trParams(
|
||||
{'count': _attachments.toString()},
|
||||
),
|
||||
'attachmentHint'.trParams({'count': _attachments.toString()}),
|
||||
style: TextStyle(color: _unFocusColor, fontSize: 12),
|
||||
)
|
||||
],
|
||||
@ -179,8 +178,8 @@ class _AttachmentListState extends State<AttachmentList> {
|
||||
final element = _attachments.first;
|
||||
double ratio = element!.metadata?['ratio']?.toDouble() ?? 16 / 9;
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: widget.columnMaxWidth,
|
||||
maxHeight: 640,
|
||||
),
|
||||
child: AspectRatio(
|
||||
@ -271,26 +270,26 @@ class _AttachmentListState extends State<AttachmentList> {
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
width: math.min(MediaQuery.of(context).size.width, widget.columnMaxWidth),
|
||||
child: CarouselSlider.builder(
|
||||
options: CarouselOptions(
|
||||
disableCenter: true,
|
||||
animateToClosest: true,
|
||||
aspectRatio: _aspectRatio,
|
||||
enlargeCenterPage: true,
|
||||
viewportFraction: widget.viewport ?? 0.95,
|
||||
enableInfiniteScroll: false,
|
||||
),
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: 320,
|
||||
),
|
||||
child: ListView.separated(
|
||||
padding: widget.padding,
|
||||
scrollDirection: Axis.horizontal,
|
||||
shrinkWrap: true,
|
||||
itemCount: _attachments.length,
|
||||
itemBuilder: (context, idx, _) {
|
||||
itemBuilder: (context, idx) {
|
||||
final element = _attachments[idx];
|
||||
if (element == null) const SizedBox.shrink();
|
||||
double ratio = element!.metadata?['ratio']?.toDouble() ?? 16 / 9;
|
||||
final ratio = element!.metadata?['ratio']?.toDouble() ?? 16 / 9;
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: widget.columnMaxWidth,
|
||||
maxHeight: 640,
|
||||
maxWidth: math.min(
|
||||
widget.columnMaxWidth,
|
||||
MediaQuery.of(context).size.width -
|
||||
(widget.padding?.horizontal ?? 0),
|
||||
),
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: ratio,
|
||||
@ -310,6 +309,7 @@ class _AttachmentListState extends State<AttachmentList> {
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, _) => const Gap(8),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -2,15 +2,21 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:solian/models/link.dart';
|
||||
import 'package:solian/providers/link_expander.dart';
|
||||
import 'package:solian/widgets/auto_cache_image.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class LinkExpansion extends StatelessWidget {
|
||||
class LinkExpansion extends StatefulWidget {
|
||||
final String content;
|
||||
|
||||
const LinkExpansion({super.key, required this.content});
|
||||
|
||||
@override
|
||||
State<LinkExpansion> createState() => _LinkExpansionState();
|
||||
}
|
||||
|
||||
class _LinkExpansionState extends State<LinkExpansion> {
|
||||
Widget _buildImage(String url, {double? width, double? height}) {
|
||||
if (url.endsWith('svg')) {
|
||||
return SvgPicture.network(url, width: width, height: height);
|
||||
@ -22,61 +28,74 @@ class LinkExpansion extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<LinkMeta>? _meta;
|
||||
|
||||
Future<void> _doExpand() async {
|
||||
final linkRegex = RegExp(
|
||||
r'(?<!\()(?:(?:https?):\/\/|www\.)(?:[-_a-z0-9]+\.)*(?:[-a-z0-9]+\.[-a-z0-9]+)[^\s<]*[^\s<?!.,:*_~]',
|
||||
);
|
||||
final matches = linkRegex.allMatches(content);
|
||||
if (matches.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final matches = linkRegex.allMatches(widget.content);
|
||||
if (matches.isEmpty) return;
|
||||
|
||||
final LinkExpandProvider expandController = Get.find();
|
||||
|
||||
if (matches.isEmpty) return;
|
||||
|
||||
List<LinkMeta> out = List.empty(growable: true);
|
||||
for (final x in matches) {
|
||||
final result = await expandController.expandLink(x.group(0)!);
|
||||
if (result != null) out.add(result);
|
||||
}
|
||||
|
||||
setState(() => _meta = out);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_doExpand();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_meta?.isEmpty ?? true) return const SizedBox.shrink();
|
||||
|
||||
return Wrap(
|
||||
children: matches.map((x) {
|
||||
children: _meta!.map((x) {
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: matches.length == 1 ? 480 : 340,
|
||||
maxWidth: _meta!.length == 1 ? 480 : 340,
|
||||
),
|
||||
child: FutureBuilder(
|
||||
future: expandController.expandLink(x.group(0)!),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final isRichDescription = [
|
||||
'solsynth.dev',
|
||||
].contains(Uri.parse(snapshot.data!.url).host);
|
||||
].contains(Uri.parse(x.url).host);
|
||||
|
||||
return GestureDetector(
|
||||
child: Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if ([
|
||||
(snapshot.data!.icon?.isNotEmpty ?? false),
|
||||
snapshot.data!.siteName != null
|
||||
].any((x) => x))
|
||||
if ([(x.icon?.isNotEmpty ?? false), x.siteName != null]
|
||||
.any((x) => x))
|
||||
Row(
|
||||
children: [
|
||||
if (snapshot.data!.icon?.isNotEmpty ?? false)
|
||||
if (x.icon?.isNotEmpty ?? false)
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
child: _buildImage(
|
||||
snapshot.data!.icon!,
|
||||
x.icon!,
|
||||
width: 32,
|
||||
height: 32,
|
||||
),
|
||||
).paddingOnly(right: 8),
|
||||
if (snapshot.data!.siteName != null)
|
||||
if (x.siteName != null)
|
||||
Expanded(
|
||||
child: Text(
|
||||
snapshot.data!.siteName!,
|
||||
x.siteName!,
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@ -84,32 +103,27 @@ class LinkExpansion extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
).paddingOnly(
|
||||
bottom: (snapshot.data!.icon?.isNotEmpty ?? false)
|
||||
? 8
|
||||
: 4,
|
||||
bottom: (x.icon?.isNotEmpty ?? false) ? 8 : 4,
|
||||
),
|
||||
if (snapshot.data!.image != null &&
|
||||
(snapshot.data!.image?.startsWith('http') ?? false))
|
||||
if (x.image != null &&
|
||||
(x.image?.startsWith('http') ?? false))
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
child: _buildImage(
|
||||
snapshot.data!.image!,
|
||||
),
|
||||
child: _buildImage(x.image!),
|
||||
).paddingOnly(bottom: 8),
|
||||
Text(
|
||||
snapshot.data!.title ?? 'No Title',
|
||||
x.title ?? 'No Title',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.fade,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
if (snapshot.data!.description != null &&
|
||||
isRichDescription)
|
||||
MarkdownBody(data: snapshot.data!.description!)
|
||||
else if (snapshot.data!.description != null)
|
||||
if (x.description != null && isRichDescription)
|
||||
MarkdownBody(data: x.description!)
|
||||
else if (x.description != null)
|
||||
Text(
|
||||
snapshot.data!.description!,
|
||||
x.description!,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@ -117,7 +131,7 @@ class LinkExpansion extends StatelessWidget {
|
||||
).paddingAll(12),
|
||||
),
|
||||
onTap: () {
|
||||
launchUrlString(x.group(0)!);
|
||||
launchUrlString(x.url);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -8,6 +8,7 @@ import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:solian/models/post.dart';
|
||||
import 'package:solian/providers/content/posts.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/screens/posts/post_detail.dart';
|
||||
import 'package:solian/shells/title_shell.dart';
|
||||
import 'package:solian/theme.dart';
|
||||
@ -34,7 +35,10 @@ class PostItem extends StatefulWidget {
|
||||
final bool isContentSelectable;
|
||||
final bool showFeaturedReply;
|
||||
final String? attachmentParent;
|
||||
|
||||
final EdgeInsets? padding;
|
||||
final Color? backgroundColor;
|
||||
|
||||
final Function? onComment;
|
||||
|
||||
const PostItem({
|
||||
@ -51,6 +55,7 @@ class PostItem extends StatefulWidget {
|
||||
this.isContentSelectable = false,
|
||||
this.showFeaturedReply = false,
|
||||
this.attachmentParent,
|
||||
this.padding,
|
||||
this.backgroundColor,
|
||||
this.onComment,
|
||||
});
|
||||
@ -126,9 +131,7 @@ class _PostItemState extends State<PostItem> {
|
||||
LinkExpansion(content: item.body['content']).paddingOnly(
|
||||
left: 8,
|
||||
right: 8,
|
||||
top: 4,
|
||||
),
|
||||
_PostFooterWidget(item: item).paddingOnly(left: 12),
|
||||
if (attachments.isNotEmpty)
|
||||
Row(
|
||||
children: [
|
||||
@ -149,9 +152,8 @@ class _PostItemState extends State<PostItem> {
|
||||
);
|
||||
}
|
||||
|
||||
return OpenContainer(
|
||||
tappable: widget.isClickable,
|
||||
closedBuilder: (_, openContainer) => Column(
|
||||
return GestureDetector(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_PostThumbnail(
|
||||
@ -220,18 +222,20 @@ class _PostItemState extends State<PostItem> {
|
||||
),
|
||||
),
|
||||
_PostFooterWidget(item: item),
|
||||
LinkExpansion(content: item.body['content']).paddingOnly(top: 4),
|
||||
LinkExpansion(content: item.body['content']),
|
||||
],
|
||||
).paddingOnly(
|
||||
right: 16,
|
||||
left: 16,
|
||||
).paddingSymmetric(
|
||||
horizontal: (widget.padding?.horizontal ?? 0) + 16,
|
||||
),
|
||||
if (hasAttachment) const Gap(8),
|
||||
_PostAttachmentWidget(
|
||||
item: item,
|
||||
padding: widget.padding,
|
||||
),
|
||||
_PostAttachmentWidget(item: item),
|
||||
if (widget.showFeaturedReply)
|
||||
_PostFeaturedReplyWidget(item: item).paddingSymmetric(
|
||||
horizontal: 12,
|
||||
horizontal: (widget.padding?.horizontal ?? 0) + 12,
|
||||
),
|
||||
if (widget.showFeaturedReply) const Gap(8),
|
||||
if (widget.isShowReply || widget.isReactable)
|
||||
PostQuickAction(
|
||||
isShowReply: widget.isShowReply,
|
||||
@ -249,22 +253,23 @@ class _PostItemState extends State<PostItem> {
|
||||
}
|
||||
},
|
||||
).paddingOnly(
|
||||
left: 14,
|
||||
right: 14,
|
||||
top: 8,
|
||||
left: (widget.padding?.left ?? 0) + 14,
|
||||
right: (widget.padding?.right ?? 0) + 14,
|
||||
)
|
||||
],
|
||||
).paddingOnly(
|
||||
top: widget.padding?.top ?? 0,
|
||||
bottom: widget.padding?.bottom ?? 0,
|
||||
),
|
||||
openBuilder: (_, __) => TitleShell(
|
||||
title: 'postDetail'.tr,
|
||||
child: PostDetailScreen(
|
||||
id: item.id.toString(),
|
||||
post: item,
|
||||
),
|
||||
),
|
||||
closedElevation: 0,
|
||||
openElevation: 0,
|
||||
closedColor: Colors.transparent,
|
||||
openColor: Theme.of(context).colorScheme.surface,
|
||||
onTap: () {
|
||||
if (widget.isClickable) {
|
||||
AppRouter.instance.pushNamed(
|
||||
'postDetail',
|
||||
pathParameters: {'id': item.id.toString()},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -293,6 +298,7 @@ class _PostFeaturedReplyWidget extends StatelessWidget {
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
constraints: const BoxConstraints(maxWidth: 480),
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
@ -389,8 +395,9 @@ class _PostFeaturedReplyWidget extends StatelessWidget {
|
||||
|
||||
class _PostAttachmentWidget extends StatelessWidget {
|
||||
final Post item;
|
||||
final EdgeInsets? padding;
|
||||
|
||||
const _PostAttachmentWidget({required this.item});
|
||||
const _PostAttachmentWidget({required this.item, required this.padding});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -402,14 +409,22 @@ class _PostAttachmentWidget extends StatelessWidget {
|
||||
|
||||
if (attachments.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
if (attachments.length == 1) {
|
||||
if (attachments.length == 1 && !isLargeScreen) {
|
||||
return AttachmentList(
|
||||
parentId: item.id.toString(),
|
||||
attachmentIds: item.preload == null ? attachments : null,
|
||||
attachments: item.preload?.attachments,
|
||||
autoload: false,
|
||||
isFullWidth: true,
|
||||
).paddingOnly(top: 4);
|
||||
);
|
||||
} else if (attachments.length == 1) {
|
||||
return AttachmentList(
|
||||
parentId: item.id.toString(),
|
||||
attachmentIds: item.preload == null ? attachments : null,
|
||||
attachments: item.preload?.attachments,
|
||||
autoload: false,
|
||||
isColumn: true,
|
||||
).paddingSymmetric(horizontal: (padding?.horizontal ?? 0) + 14);
|
||||
} else if (attachments.length > 1 &&
|
||||
attachments.length % 3 == 0 &&
|
||||
!isLargeScreen) {
|
||||
@ -419,14 +434,17 @@ class _PostAttachmentWidget extends StatelessWidget {
|
||||
attachments: item.preload?.attachments,
|
||||
autoload: false,
|
||||
isGrid: true,
|
||||
).paddingSymmetric(horizontal: 14, vertical: 8);
|
||||
).paddingSymmetric(horizontal: (padding?.horizontal ?? 0) + 14);
|
||||
} else {
|
||||
return AttachmentList(
|
||||
parentId: item.id.toString(),
|
||||
attachmentIds: item.preload == null ? attachments : null,
|
||||
attachments: item.preload?.attachments,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: (padding?.horizontal ?? 0) + 14,
|
||||
),
|
||||
autoload: false,
|
||||
).paddingOnly(bottom: 8, top: 4);
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -568,7 +586,7 @@ class _PostFooterWidget extends StatelessWidget {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: widgets,
|
||||
).paddingOnly(top: 4);
|
||||
).paddingSymmetric(vertical: 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,8 +60,9 @@ class PostListEntryWidget extends StatelessWidget {
|
||||
final bool isClickable;
|
||||
final bool showFeaturedReply;
|
||||
final Post item;
|
||||
final Function onUpdate;
|
||||
final Color? backgroundColor;
|
||||
final EdgeInsets? padding;
|
||||
final Function onUpdate;
|
||||
|
||||
const PostListEntryWidget({
|
||||
super.key,
|
||||
@ -70,8 +71,9 @@ class PostListEntryWidget extends StatelessWidget {
|
||||
required this.isClickable,
|
||||
required this.showFeaturedReply,
|
||||
required this.item,
|
||||
required this.onUpdate,
|
||||
this.backgroundColor,
|
||||
this.padding,
|
||||
required this.onUpdate,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -83,6 +85,7 @@ class PostListEntryWidget extends StatelessWidget {
|
||||
isShowEmbed: isShowEmbed,
|
||||
isClickable: isNestedClickable,
|
||||
showFeaturedReply: showFeaturedReply,
|
||||
padding: padding,
|
||||
backgroundColor: backgroundColor,
|
||||
onComment: () {
|
||||
AppRouter.instance
|
||||
@ -129,6 +132,7 @@ class ControlledPostListWidget extends StatelessWidget {
|
||||
final bool isNestedClickable;
|
||||
final bool isPinned;
|
||||
final PagingController<int, Post> controller;
|
||||
final EdgeInsets? padding;
|
||||
final Function? onUpdate;
|
||||
|
||||
const ControlledPostListWidget({
|
||||
@ -138,6 +142,7 @@ class ControlledPostListWidget extends StatelessWidget {
|
||||
this.isClickable = true,
|
||||
this.isNestedClickable = true,
|
||||
this.isPinned = true,
|
||||
this.padding,
|
||||
this.onUpdate,
|
||||
});
|
||||
|
||||
@ -156,6 +161,7 @@ class ControlledPostListWidget extends StatelessWidget {
|
||||
isNestedClickable: isNestedClickable,
|
||||
isClickable: isClickable,
|
||||
showFeaturedReply: true,
|
||||
padding: padding,
|
||||
item: item,
|
||||
onUpdate: onUpdate ?? () {},
|
||||
);
|
||||
|
@ -8,11 +8,13 @@ import 'package:solian/widgets/posts/post_list.dart';
|
||||
|
||||
class PostReplyList extends StatefulWidget {
|
||||
final Post item;
|
||||
final EdgeInsets? padding;
|
||||
final Color? backgroundColor;
|
||||
|
||||
const PostReplyList({
|
||||
super.key,
|
||||
required this.item,
|
||||
this.padding,
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
@ -53,7 +55,7 @@ class _PostReplyListState extends State<PostReplyList> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PostListWidget(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||
padding: widget.padding,
|
||||
isShowEmbed: false,
|
||||
controller: _pagingController,
|
||||
backgroundColor: widget.backgroundColor,
|
||||
@ -93,6 +95,7 @@ class PostReplyListPopup extends StatelessWidget {
|
||||
slivers: [
|
||||
PostReplyList(
|
||||
item: item,
|
||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surfaceContainerLow,
|
||||
),
|
||||
|
12
pubspec.lock
12
pubspec.lock
@ -198,14 +198,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
carousel_slider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: carousel_slider
|
||||
sha256: "7b006ec356205054af5beaef62e2221160ea36b90fb70a35e4deacd49d0349ae"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -2278,10 +2270,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "4d45dc9069dba4619dc0ebd93c7cec5e66d8482cb625a370ac806dcc8165f2ec"
|
||||
sha256: e5c39a90447e7c81cfec14b041cdbd0d0916bd9ebbc7fe02ab69568be703b9bd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.5.5"
|
||||
version: "5.6.0"
|
||||
win32_registry:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -18,7 +18,6 @@ dependencies:
|
||||
flutter_markdown: ^0.7.1
|
||||
flutter_animate: ^4.5.0
|
||||
flutter_secure_storage: ^9.2.1
|
||||
carousel_slider: ^5.0.0
|
||||
url_launcher: ^6.2.6
|
||||
infinite_scroll_pagination: ^4.0.0
|
||||
image_picker: ^1.1.1
|
||||
|
Loading…
Reference in New Issue
Block a user