mirror of
https://github.com/Iconica-Development/flutter_timeline.git
synced 2025-05-19 18:43:45 +02:00
feat: add refreshindicator for postdetail
This commit is contained in:
parent
753ecc039e
commit
f587e81a4b
3 changed files with 253 additions and 221 deletions
|
@ -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)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue