feat: add refreshindicator for postdetail

This commit is contained in:
Freek van de Ven 2023-11-21 23:04:56 +01:00
parent 753ecc039e
commit f587e81a4b
3 changed files with 253 additions and 221 deletions

View file

@ -94,6 +94,23 @@ class FirebaseTimelineService with ChangeNotifier implements TimelineService {
return posts;
}
@override
Future<TimelinePost> fetchPost(TimelinePost post) async {
var doc = await _db
.collection(_options.timelineCollectionName)
.doc(post.id)
.get();
var data = doc.data();
if (data == null) return post;
var user = await _userService.getUser(data['creator_id']);
var updatedPost = TimelinePost.fromJson(doc.id, data).copyWith(
creator: user,
);
_posts = _posts.map((p) => (p.id == post.id) ? updatedPost : p).toList();
notifyListeners();
return updatedPost;
}
@override
List<TimelinePost> getPosts(String? category) => _posts
.where((element) => category == null || element.category == category)

View file

@ -12,6 +12,7 @@ abstract class TimelineService with ChangeNotifier {
Future<void> deletePost(TimelinePost post);
Future<TimelinePost> createPost(TimelinePost post);
Future<List<TimelinePost>> fetchPosts(String? category);
Future<TimelinePost> fetchPost(TimelinePost post);
List<TimelinePost> getPosts(String? category);
Future<TimelinePost> fetchPostDetails(TimelinePost post);
Future<TimelinePost> reactToPost(

View file

@ -111,240 +111,254 @@ class _TimelinePostScreenState extends State<TimelinePostScreen> {
return Stack(
children: [
SingleChildScrollView(
child: Padding(
padding: widget.padding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
if (post.creator != null)
InkWell(
onTap: widget.onUserTap != null
? () => widget.onUserTap?.call(post.creator!.userId)
: null,
child: Row(
children: [
if (post.creator!.imageUrl != null) ...[
widget.options.userAvatarBuilder?.call(
post.creator!,
40,
) ??
CircleAvatar(
radius: 20,
backgroundImage: CachedNetworkImageProvider(
post.creator!.imageUrl!,
),
),
],
const SizedBox(width: 10),
if (post.creator!.fullName != null) ...[
Text(
post.creator!.fullName!,
style: theme.textTheme.titleMedium,
),
],
],
),
),
const Spacer(),
if (widget.options.allowAllDeletion ||
post.creator?.userId == widget.userId)
PopupMenuButton(
onSelected: (value) async {
if (value == 'delete') {
await widget.service.deletePost(post);
widget.onPostDelete();
}
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: 'delete',
child: Row(
children: [
Text(widget.options.translations.deletePost),
const SizedBox(width: 8),
widget.options.theme.deleteIcon ??
Icon(
Icons.delete,
color: widget.options.theme.iconColor,
RefreshIndicator(
onRefresh: () async {
updatePost(
await widget.service.fetchPostDetails(
await widget.service.fetchPost(
post,
),
),
);
},
child: SingleChildScrollView(
child: Padding(
padding: widget.padding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
if (post.creator != null)
InkWell(
onTap: widget.onUserTap != null
? () =>
widget.onUserTap?.call(post.creator!.userId)
: null,
child: Row(
children: [
if (post.creator!.imageUrl != null) ...[
widget.options.userAvatarBuilder?.call(
post.creator!,
40,
) ??
CircleAvatar(
radius: 20,
backgroundImage:
CachedNetworkImageProvider(
post.creator!.imageUrl!,
),
),
],
),
),
],
child: widget.options.theme.moreIcon ??
Icon(
Icons.more_horiz_rounded,
color: widget.options.theme.iconColor,
),
),
],
),
const SizedBox(height: 8),
// image of the post
if (post.imageUrl != null) ...[
CachedNetworkImage(
imageUrl: post.imageUrl!,
width: double.infinity,
fit: BoxFit.fitHeight,
),
],
// post information
Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
children: [
if (post.likedBy?.contains(widget.userId) ?? false) ...[
InkWell(
onTap: () async {
updatePost(
await widget.service.unlikePost(
widget.userId,
post,
),
);
},
child: widget.options.theme.likedIcon ??
Icon(
Icons.thumb_up_rounded,
color: widget.options.theme.iconColor,
),
),
] else ...[
InkWell(
onTap: () async {
updatePost(
await widget.service.likePost(
widget.userId,
post,
),
);
},
child: widget.options.theme.likeIcon ??
Icon(
Icons.thumb_up_alt_outlined,
color: widget.options.theme.iconColor,
),
),
],
const SizedBox(width: 8),
if (post.reactionEnabled)
widget.options.theme.commentIcon ??
Icon(
Icons.chat_bubble_outline_rounded,
color: widget.options.theme.iconColor,
),
],
),
),
Text(
'${post.likes} ${widget.options.translations.likesTitle}',
style: theme.textTheme.titleSmall,
),
const SizedBox(height: 4),
Text.rich(
TextSpan(
text: post.creator?.fullName ??
widget.options.translations.anonymousUser,
style: theme.textTheme.titleSmall,
children: [
const TextSpan(text: ' '),
TextSpan(
text: post.title,
style: theme.textTheme.bodyMedium,
),
],
),
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
post.content,
style: theme.textTheme.bodyMedium,
),
const SizedBox(height: 4),
Text(
'${dateFormat.format(post.createdAt)} '
'${widget.options.translations.postAt} '
'${timeFormat.format(post.createdAt)}',
style: theme.textTheme.bodySmall,
),
const SizedBox(height: 12),
if (post.reactionEnabled) ...[
Text(
widget.options.translations.commentsTitle,
style: theme.textTheme.displaySmall,
),
for (var reaction
in post.reactions ?? <TimelinePostReaction>[]) ...[
const SizedBox(height: 16),
Row(
crossAxisAlignment: reaction.imageUrl != null
? CrossAxisAlignment.start
: CrossAxisAlignment.center,
children: [
if (reaction.creator?.imageUrl != null &&
reaction.creator!.imageUrl!.isNotEmpty) ...[
widget.options.userAvatarBuilder?.call(
reaction.creator!,
25,
) ??
CircleAvatar(
radius: 20,
backgroundImage: CachedNetworkImageProvider(
reaction.creator!.imageUrl!,
),
),
],
const SizedBox(width: 10),
if (reaction.imageUrl != null) ...[
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(width: 10),
if (post.creator!.fullName != null) ...[
Text(
reaction.creator?.fullName ??
widget.options.translations.anonymousUser,
style: theme.textTheme.titleSmall,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: CachedNetworkImage(
imageUrl: reaction.imageUrl!,
fit: BoxFit.fitWidth,
),
post.creator!.fullName!,
style: theme.textTheme.titleMedium,
),
],
),
],
),
] else ...[
Expanded(
child: Text.rich(
TextSpan(
text: reaction.creator?.fullName ??
widget.options.translations.anonymousUser,
style: theme.textTheme.titleSmall,
),
const Spacer(),
if (widget.options.allowAllDeletion ||
post.creator?.userId == widget.userId)
PopupMenuButton(
onSelected: (value) async {
if (value == 'delete') {
await widget.service.deletePost(post);
widget.onPostDelete();
}
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: 'delete',
child: Row(
children: [
const TextSpan(text: ' '),
TextSpan(
text: reaction.reaction ?? '',
style: theme.textTheme.bodyMedium,
),
// text should go to new line
Text(widget.options.translations.deletePost),
const SizedBox(width: 8),
widget.options.theme.deleteIcon ??
Icon(
Icons.delete,
color: widget.options.theme.iconColor,
),
],
),
),
),
],
],
],
child: widget.options.theme.moreIcon ??
Icon(
Icons.more_horiz_rounded,
color: widget.options.theme.iconColor,
),
),
],
),
const SizedBox(height: 8),
// image of the post
if (post.imageUrl != null) ...[
CachedNetworkImage(
imageUrl: post.imageUrl!,
width: double.infinity,
fit: BoxFit.fitHeight,
),
],
const SizedBox(height: 120),
// post information
Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
children: [
if (post.likedBy?.contains(widget.userId) ?? false) ...[
InkWell(
onTap: () async {
updatePost(
await widget.service.unlikePost(
widget.userId,
post,
),
);
},
child: widget.options.theme.likedIcon ??
Icon(
Icons.thumb_up_rounded,
color: widget.options.theme.iconColor,
),
),
] else ...[
InkWell(
onTap: () async {
updatePost(
await widget.service.likePost(
widget.userId,
post,
),
);
},
child: widget.options.theme.likeIcon ??
Icon(
Icons.thumb_up_alt_outlined,
color: widget.options.theme.iconColor,
),
),
],
const SizedBox(width: 8),
if (post.reactionEnabled)
widget.options.theme.commentIcon ??
Icon(
Icons.chat_bubble_outline_rounded,
color: widget.options.theme.iconColor,
),
],
),
),
Text(
'${post.likes} ${widget.options.translations.likesTitle}',
style: theme.textTheme.titleSmall,
),
const SizedBox(height: 4),
Text.rich(
TextSpan(
text: post.creator?.fullName ??
widget.options.translations.anonymousUser,
style: theme.textTheme.titleSmall,
children: [
const TextSpan(text: ' '),
TextSpan(
text: post.title,
style: theme.textTheme.bodyMedium,
),
],
),
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
post.content,
style: theme.textTheme.bodyMedium,
),
const SizedBox(height: 4),
Text(
'${dateFormat.format(post.createdAt)} '
'${widget.options.translations.postAt} '
'${timeFormat.format(post.createdAt)}',
style: theme.textTheme.bodySmall,
),
const SizedBox(height: 12),
if (post.reactionEnabled) ...[
Text(
widget.options.translations.commentsTitle,
style: theme.textTheme.displaySmall,
),
for (var reaction
in post.reactions ?? <TimelinePostReaction>[]) ...[
const SizedBox(height: 16),
Row(
crossAxisAlignment: reaction.imageUrl != null
? CrossAxisAlignment.start
: CrossAxisAlignment.center,
children: [
if (reaction.creator?.imageUrl != null &&
reaction.creator!.imageUrl!.isNotEmpty) ...[
widget.options.userAvatarBuilder?.call(
reaction.creator!,
25,
) ??
CircleAvatar(
radius: 20,
backgroundImage: CachedNetworkImageProvider(
reaction.creator!.imageUrl!,
),
),
],
const SizedBox(width: 10),
if (reaction.imageUrl != null) ...[
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
reaction.creator?.fullName ??
widget
.options.translations.anonymousUser,
style: theme.textTheme.titleSmall,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: CachedNetworkImage(
imageUrl: reaction.imageUrl!,
fit: BoxFit.fitWidth,
),
),
],
),
),
] else ...[
Expanded(
child: Text.rich(
TextSpan(
text: reaction.creator?.fullName ??
widget.options.translations.anonymousUser,
style: theme.textTheme.titleSmall,
children: [
const TextSpan(text: ' '),
TextSpan(
text: reaction.reaction ?? '',
style: theme.textTheme.bodyMedium,
),
// text should go to new line
],
),
),
),
],
],
),
],
const SizedBox(height: 120),
],
],
],
),
),
),
),