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; 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 @override
List<TimelinePost> getPosts(String? category) => _posts List<TimelinePost> getPosts(String? category) => _posts
.where((element) => category == null || element.category == category) .where((element) => category == null || element.category == category)

View file

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

View file

@ -111,240 +111,254 @@ class _TimelinePostScreenState extends State<TimelinePostScreen> {
return Stack( return Stack(
children: [ children: [
SingleChildScrollView( RefreshIndicator(
child: Padding( onRefresh: () async {
padding: widget.padding, updatePost(
child: Column( await widget.service.fetchPostDetails(
crossAxisAlignment: CrossAxisAlignment.start, await widget.service.fetchPost(
children: [ post,
Row( ),
children: [ ),
if (post.creator != null) );
InkWell( },
onTap: widget.onUserTap != null child: SingleChildScrollView(
? () => widget.onUserTap?.call(post.creator!.userId) child: Padding(
: null, padding: widget.padding,
child: Row( child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
if (post.creator!.imageUrl != null) ...[ children: [
widget.options.userAvatarBuilder?.call( Row(
post.creator!, children: [
40, if (post.creator != null)
) ?? InkWell(
CircleAvatar( onTap: widget.onUserTap != null
radius: 20, ? () =>
backgroundImage: CachedNetworkImageProvider( widget.onUserTap?.call(post.creator!.userId)
post.creator!.imageUrl!, : null,
), child: Row(
), children: [
], if (post.creator!.imageUrl != null) ...[
const SizedBox(width: 10), widget.options.userAvatarBuilder?.call(
if (post.creator!.fullName != null) ...[ post.creator!,
Text( 40,
post.creator!.fullName!, ) ??
style: theme.textTheme.titleMedium, CircleAvatar(
), radius: 20,
], backgroundImage:
], CachedNetworkImageProvider(
), post.creator!.imageUrl!,
), ),
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,
), ),
], ],
), const SizedBox(width: 10),
), if (post.creator!.fullName != null) ...[
],
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: [
Text( Text(
reaction.creator?.fullName ?? post.creator!.fullName!,
widget.options.translations.anonymousUser, style: theme.textTheme.titleMedium,
style: theme.textTheme.titleSmall,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: CachedNetworkImage(
imageUrl: reaction.imageUrl!,
fit: BoxFit.fitWidth,
),
), ),
], ],
), ],
), ),
] else ...[ ),
Expanded( const Spacer(),
child: Text.rich( if (widget.options.allowAllDeletion ||
TextSpan( post.creator?.userId == widget.userId)
text: reaction.creator?.fullName ?? PopupMenuButton(
widget.options.translations.anonymousUser, onSelected: (value) async {
style: theme.textTheme.titleSmall, if (value == 'delete') {
await widget.service.deletePost(post);
widget.onPostDelete();
}
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: 'delete',
child: Row(
children: [ children: [
const TextSpan(text: ' '), Text(widget.options.translations.deletePost),
TextSpan( const SizedBox(width: 8),
text: reaction.reaction ?? '', widget.options.theme.deleteIcon ??
style: theme.textTheme.bodyMedium, Icon(
), Icons.delete,
// text should go to new line 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),
],
], ],
], ),
), ),
), ),
), ),