diff --git a/packages/flutter_timeline/lib/src/models/timeline_configuration.dart b/packages/flutter_timeline/lib/src/models/timeline_configuration.dart index 0904981..748f641 100644 --- a/packages/flutter_timeline/lib/src/models/timeline_configuration.dart +++ b/packages/flutter_timeline/lib/src/models/timeline_configuration.dart @@ -28,7 +28,11 @@ class TimelineUserStoryConfiguration { final Widget Function(BuildContext context, Widget filterBar, Widget child)? mainPageBuilder; - final Widget Function(BuildContext context, Widget child, TimelineCategory category)? postScreenBuilder; + final Widget Function( + BuildContext context, + Widget child, + TimelineCategory category, + )? postScreenBuilder; final Widget Function(BuildContext context, Widget child)? postCreationScreenBuilder; diff --git a/packages/flutter_timeline_firebase/lib/src/service/firebase_timeline_service.dart b/packages/flutter_timeline_firebase/lib/src/service/firebase_timeline_service.dart index cc91609..0bcbaf2 100644 --- a/packages/flutter_timeline_firebase/lib/src/service/firebase_timeline_service.dart +++ b/packages/flutter_timeline_firebase/lib/src/service/firebase_timeline_service.dart @@ -60,6 +60,30 @@ class FirebaseTimelineService with ChangeNotifier implements TimelineService { notifyListeners(); } + @override + Future deletePostReaction( + TimelinePost post, + String reactionId, + ) async { + var updatedPost = post.copyWith( + reaction: post.reaction - 1, + reactions: (post.reactions ?? []) + ..removeWhere((element) => element.id == reactionId), + ); + _posts = _posts + .map( + (p) => p.id == post.id ? updatedPost : p, + ) + .toList(); + var postRef = _db.collection(_options.timelineCollectionName).doc(post.id); + await postRef.update({ + 'reaction': FieldValue.increment(-1), + 'reactions': FieldValue.arrayRemove([reactionId]), + }); + notifyListeners(); + return updatedPost; + } + @override Future fetchPostDetails(TimelinePost post) async { var reactions = post.reactions ?? []; diff --git a/packages/flutter_timeline_interface/lib/src/services/timeline_service.dart b/packages/flutter_timeline_interface/lib/src/services/timeline_service.dart index 9606f43..2f7e843 100644 --- a/packages/flutter_timeline_interface/lib/src/services/timeline_service.dart +++ b/packages/flutter_timeline_interface/lib/src/services/timeline_service.dart @@ -10,6 +10,7 @@ import 'package:flutter_timeline_interface/src/model/timeline_reaction.dart'; abstract class TimelineService with ChangeNotifier { Future deletePost(TimelinePost post); + Future deletePostReaction(TimelinePost post, String reactionId); Future createPost(TimelinePost post); Future> fetchPosts(String? category); Future fetchPost(TimelinePost post); diff --git a/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart b/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart index d0508ae..19ec269 100644 --- a/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart +++ b/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart @@ -20,6 +20,7 @@ class TimelineTranslations { 'Indicate whether people are allowed to respond', this.checkPost = 'Check post overview', this.deletePost = 'Delete post', + this.deleteReaction = 'Delete Reaction', this.viewPost = 'View post', this.likesTitle = 'Likes', this.commentsTitle = 'Comments', @@ -45,6 +46,7 @@ class TimelineTranslations { final String postAt; final String deletePost; + final String deleteReaction; final String viewPost; final String likesTitle; final String commentsTitle; diff --git a/packages/flutter_timeline_view/lib/src/screens/timeline_post_screen.dart b/packages/flutter_timeline_view/lib/src/screens/timeline_post_screen.dart index e1d238e..47f4b8e 100644 --- a/packages/flutter_timeline_view/lib/src/screens/timeline_post_screen.dart +++ b/packages/flutter_timeline_view/lib/src/screens/timeline_post_screen.dart @@ -312,66 +312,99 @@ class _TimelinePostScreenState extends State { for (var reaction in post.reactions ?? []) ...[ 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!, + GestureDetector( + onLongPress: () async { + if (reaction.creatorId == widget.userId || + widget.options.allowAllDeletion) { + // Show popup menu for deletion + var value = await showMenu( + context: context, + position: const RelativeRect.fromLTRB( + 100.0, + 200.0, + 100.0, + 100.0, + ), + items: [ + PopupMenuItem( + value: 'delete', + child: Text( + widget.options.translations.deleteReaction, ), ), - ], - 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, + ], + ); + if (value == 'delete') { + // Call service to delete reaction + updatePost( + await widget.service + .deletePostReaction(post, reaction.id), + ); + } + } + }, + child: 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!, ), ), - ], - ), - ), - ] else ...[ - Expanded( - child: Text.rich( - TextSpan( - text: reaction.creator?.fullName ?? - widget.options.translations.anonymousUser, - style: theme.textTheme.titleSmall, + ], + const SizedBox(width: 10), + if (reaction.imageUrl != null) ...[ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - const TextSpan(text: ' '), - TextSpan( - text: reaction.reaction ?? '', - style: theme.textTheme.bodyMedium, + 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, + ), ), - // text should go to new line ], ), ), - ), + ] 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 + ], + ), + ), + ), + ], ], - ], + ), ), ], if (post.reactions?.isEmpty ?? true) ...[