Skip to content

Commit

Permalink
Creating comments now inserts the comment into the comment tree inste…
Browse files Browse the repository at this point in the history
…ad of refreshing page. The newly created comment is also highlighted. When commenting from a comment context view, the highlighted comment changes to the newly created comment. If the comment is a top level comment, then it gets inserted at the very top and the scroll controller is then scrolled to the top. If the comment is a direct reply, it gets appended immediately under the parent and the scroll position is maintained.
  • Loading branch information
ajsosa committed Sep 9, 2023
1 parent d36a033 commit 0e25a8e
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 29 deletions.
45 changes: 24 additions & 21 deletions lib/post/bloc/post_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ class PostBloc extends Bloc<PostEvent, PostState> {

while (attemptCount < 2) {
try {
emit(state.copyWith(status: PostStatus.loading, selectedCommentPath: event.selectedCommentPath, selectedCommentId: event.selectedCommentId));
emit(state.copyWith(
status: PostStatus.loading, selectedCommentPath: event.selectedCommentPath, selectedCommentId: event.selectedCommentId, newlyCreatedCommentId: event.newlyCreatedCommentId));

LemmyApiV3 lemmy = LemmyClient.instance.lemmyApiV3;

Expand Down Expand Up @@ -126,18 +127,18 @@ class PostBloc extends Bloc<PostEvent, PostState> {
}
}

emit(
state.copyWith(
status: PostStatus.success,
postId: postView?.postView.post.id,
postView: postView,
communityId: postView?.postView.post.communityId,
moderators: moderators,
selectedCommentPath: event.selectedCommentPath,
selectedCommentId: event.selectedCommentId),
);
emit(state.copyWith(
status: PostStatus.success,
postId: postView?.postView.post.id,
postView: postView,
communityId: postView?.postView.post.communityId,
moderators: moderators,
selectedCommentPath: event.selectedCommentPath,
selectedCommentId: event.selectedCommentId,
newlyCreatedCommentId: event.newlyCreatedCommentId));

emit(state.copyWith(status: PostStatus.refreshing, selectedCommentPath: event.selectedCommentPath, selectedCommentId: event.selectedCommentId));
emit(state.copyWith(
status: PostStatus.refreshing, selectedCommentPath: event.selectedCommentPath, selectedCommentId: event.selectedCommentId, newlyCreatedCommentId: event.newlyCreatedCommentId));

CommentSortType sortType = event.sortType ?? (state.sortType ?? defaultSortType);

Expand Down Expand Up @@ -183,7 +184,8 @@ class PostBloc extends Bloc<PostEvent, PostState> {
communityId: postView?.postView.post.communityId,
sortType: sortType,
selectedCommentId: event.selectedCommentId,
selectedCommentPath: event.selectedCommentPath),
selectedCommentPath: event.selectedCommentPath,
newlyCreatedCommentId: event.newlyCreatedCommentId),
);
} catch (e) {
exception = e;
Expand Down Expand Up @@ -469,17 +471,18 @@ class PostBloc extends Bloc<PostEvent, PostState> {
int? selectedCommentId = event.selectedCommentId;
String? selectedCommentPath = event.selectedCommentPath;

// for now, refresh the post and refetch the comments
// @todo: insert the new comment in place without requiring a refetch
// @todo: alternatively, insert and scroll to new comment on refetch
if (event.parentCommentId != null) {
add(GetPostEvent(postView: state.postView!, selectedCommentId: selectedCommentId, selectedCommentPath: selectedCommentPath));
} else {
List<CommentViewTree> updatedComments = insertNewComment(state.comments, createComment.commentView);

if (event.parentCommentId == null) {
selectedCommentId = null;
selectedCommentPath = null;
add(GetPostEvent(postView: state.postView!, selectedCommentId: selectedCommentId, selectedCommentPath: selectedCommentPath));
}
return emit(state.copyWith(status: PostStatus.success, selectedCommentId: selectedCommentId, selectedCommentPath: selectedCommentPath));
return emit(state.copyWith(
status: PostStatus.success,
comments: updatedComments,
selectedCommentId: selectedCommentId,
selectedCommentPath: selectedCommentPath,
newlyCreatedCommentId: createComment.commentView.comment.id));
} catch (e) {
return emit(state.copyWith(status: PostStatus.failure, errorMessage: e.toString()));
}
Expand Down
3 changes: 2 additions & 1 deletion lib/post/bloc/post_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ class GetPostEvent extends PostEvent {
final CommentSortType? sortType;
final String? selectedCommentPath;
final int? selectedCommentId;
final int? newlyCreatedCommentId;

const GetPostEvent({this.sortType, this.postView, this.postId, this.selectedCommentPath, this.selectedCommentId});
const GetPostEvent({this.sortType, this.postView, this.postId, this.selectedCommentPath, this.selectedCommentId, this.newlyCreatedCommentId});
}

class GetPostCommentsEvent extends PostEvent {
Expand Down
7 changes: 7 additions & 0 deletions lib/post/bloc/post_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class PostState extends Equatable {
this.sortTypeIcon,
this.selectedCommentId,
this.selectedCommentPath,
this.newlyCreatedCommentId,
this.moddingCommentId = -1,
this.viewAllCommentsRefresh = false,
this.navigateCommentIndex = 0,
Expand All @@ -44,14 +45,17 @@ class PostState extends Equatable {
final int commentCount;
final bool hasReachedCommentEnd;
final int? selectedCommentId;
final int? newlyCreatedCommentId;
final String? selectedCommentPath;

// This is to track what comment is being restored or deleted so we can
// show a spinner indicator that thunder is working on it
final int moddingCommentId;

final String? errorMessage;

final int navigateCommentIndex;

// This exists purely for forcing the bloc to refire
// even if the comment index doesn't change
final int navigateCommentId;
Expand All @@ -72,6 +76,7 @@ class PostState extends Equatable {
IconData? sortTypeIcon,
int? selectedCommentId,
String? selectedCommentPath,
int? newlyCreatedCommentId,
int? moddingCommentId,
bool? viewAllCommentsRefresh = false,
int? navigateCommentIndex,
Expand All @@ -93,6 +98,7 @@ class PostState extends Equatable {
sortTypeIcon: sortTypeIcon ?? this.sortTypeIcon,
selectedCommentId: selectedCommentId,
selectedCommentPath: selectedCommentPath,
newlyCreatedCommentId: newlyCreatedCommentId,
moddingCommentId: moddingCommentId ?? this.moddingCommentId,
viewAllCommentsRefresh: viewAllCommentsRefresh ?? false,
navigateCommentIndex: navigateCommentIndex ?? 0,
Expand All @@ -116,6 +122,7 @@ class PostState extends Equatable {
sortTypeIcon,
selectedCommentId,
selectedCommentPath,
newlyCreatedCommentId,
viewAllCommentsRefresh,
moddingCommentId,
navigateCommentIndex,
Expand Down
1 change: 1 addition & 0 deletions lib/post/pages/post_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ class _PostPageState extends State<PostPage> {
comments: state.comments,
selectedCommentId: state.selectedCommentId,
selectedCommentPath: state.selectedCommentPath,
newlyCreatedCommentId: state.newlyCreatedCommentId,
moddingCommentId: state.moddingCommentId,
viewFullCommentsRefreshing: state.viewAllCommentsRefresh,
itemScrollController: _itemScrollController,
Expand Down
3 changes: 3 additions & 0 deletions lib/post/pages/post_page_success.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class PostPageSuccess extends StatefulWidget {
final List<CommentViewTree> comments;
final int? selectedCommentId;
final String? selectedCommentPath;
final int? newlyCreatedCommentId;
final int? moddingCommentId;

final ItemScrollController itemScrollController;
Expand All @@ -47,6 +48,7 @@ class PostPageSuccess extends StatefulWidget {
this.hasReachedCommentEnd = false,
this.selectedCommentId,
this.selectedCommentPath,
this.newlyCreatedCommentId,
this.moddingCommentId,
this.viewFullCommentsRefreshing = false,
required this.moderators,
Expand Down Expand Up @@ -85,6 +87,7 @@ class _PostPageSuccessState extends State<PostPageSuccess> {
moddingCommentId: widget.moddingCommentId,
selectedCommentId: widget.selectedCommentId,
selectedCommentPath: widget.selectedCommentPath,
newlyCreatedCommentId: widget.newlyCreatedCommentId,
now: DateTime.now().toUtc(),
itemScrollController: widget.itemScrollController,
itemPositionsListener: widget.itemPositionsListener,
Expand Down
17 changes: 10 additions & 7 deletions lib/post/widgets/comment_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import 'package:thunder/core/enums/nested_comment_indicator.dart';
import 'package:thunder/core/enums/swipe_action.dart';
import 'package:thunder/post/bloc/post_bloc.dart';
import 'package:thunder/post/utils/comment_actions.dart';
import 'package:thunder/shared/comment_card_actions.dart';
import 'package:thunder/shared/comment_header.dart';
import 'package:thunder/shared/common_markdown_body.dart';
import 'package:thunder/core/auth/bloc/auth_bloc.dart';
import 'package:thunder/core/models/comment_view_tree.dart';
import 'package:thunder/thunder/bloc/thunder_bloc.dart';
Expand All @@ -29,6 +26,7 @@ class CommentCard extends StatefulWidget {
final Set collapsedCommentSet;
final int? selectCommentId;
final String? selectedCommentPath;
final int? newlyCreatedCommentId;
final int? moddingCommentId;

final DateTime now;
Expand All @@ -48,6 +46,7 @@ class CommentCard extends StatefulWidget {
this.collapsedCommentSet = const {},
this.selectCommentId,
this.selectedCommentPath,
this.newlyCreatedCommentId,
this.moddingCommentId,
required this.onDeleteAction,
required this.moderators,
Expand Down Expand Up @@ -140,12 +139,15 @@ class _CommentCardState extends State<CommentCard> with SingleTickerProviderStat

// Checks for the same creator id to user id
final bool isOwnComment = widget.commentViewTree.commentView?.creator.id == context.read<AuthBloc>().state.account?.userId;

final bool isUserLoggedIn = context.read<AuthBloc>().state.isLoggedIn;

final ThunderState state = context.read<ThunderBloc>().state;

bool collapseParentCommentOnGesture = state.collapseParentCommentOnGesture;
final int? commentId = widget.commentViewTree.commentView?.comment.id;
bool highlightComment = false;
if (widget.selectCommentId == commentId && widget.newlyCreatedCommentId == null || widget.newlyCreatedCommentId == commentId) {
highlightComment = true;
}

NestedCommentIndicatorStyle nestedCommentIndicatorStyle = state.nestedCommentIndicatorStyle;
NestedCommentIndicatorColor nestedCommentIndicatorColor = state.nestedCommentIndicatorColor;

Expand Down Expand Up @@ -335,7 +337,7 @@ class _CommentCardState extends State<CommentCard> with SingleTickerProviderStat
),
),
child: Material(
color: widget.selectCommentId == widget.commentViewTree.commentView!.comment.id ? theme.highlightColor : theme.colorScheme.background,
color: highlightComment ? theme.highlightColor : theme.colorScheme.background,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
Expand Down Expand Up @@ -442,6 +444,7 @@ class _CommentCardState extends State<CommentCard> with SingleTickerProviderStat
moddingCommentId: widget.moddingCommentId,
selectedCommentPath: widget.selectedCommentPath,
selectCommentId: widget.selectCommentId,
newlyCreatedCommentId: widget.newlyCreatedCommentId,
now: widget.now,
commentViewTree: widget.commentViewTree.replies[index],
collapsedCommentSet: widget.collapsedCommentSet,
Expand Down
10 changes: 10 additions & 0 deletions lib/post/widgets/comment_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class CommentSubview extends StatefulWidget {
final PostViewMedia? postViewMedia;
final int? selectedCommentId;
final String? selectedCommentPath;
final int? newlyCreatedCommentId;
final int? moddingCommentId;
final ItemScrollController itemScrollController;
final ItemPositionsListener itemPositionsListener;
Expand All @@ -46,6 +47,7 @@ class CommentSubview extends StatefulWidget {
this.postViewMedia,
this.selectedCommentId,
this.selectedCommentPath,
this.newlyCreatedCommentId,
this.moddingCommentId,
required this.itemScrollController,
required this.itemPositionsListener,
Expand Down Expand Up @@ -107,6 +109,13 @@ class _CommentSubviewState extends State<CommentSubview> with SingleTickerProvid
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
);
} else if (state.newlyCreatedCommentId != null && state.comments.first.commentView?.comment.id == state.newlyCreatedCommentId) {
// Only scroll for top level comments since you can comment from anywhere in the comment section.
widget.itemScrollController.scrollTo(
index: 1,
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
);
}
},
child: ScrollablePositionedList.builder(
Expand Down Expand Up @@ -172,6 +181,7 @@ class _CommentSubviewState extends State<CommentSubview> with SingleTickerProvid
now: widget.now,
selectCommentId: widget.selectedCommentId,
selectedCommentPath: widget.selectedCommentPath,
newlyCreatedCommentId: widget.newlyCreatedCommentId,
moddingCommentId: widget.moddingCommentId,
commentViewTree: widget.comments[index - 1],
collapsedCommentSet: collapsedCommentSet,
Expand Down
43 changes: 43 additions & 0 deletions lib/utils/comment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,49 @@ List<CommentViewTree> buildCommentViewTree(List<CommentView> comments, {bool fla
return commentMap.values.where((commentView) => commentView.commentView!.comment.path.isEmpty || commentView.commentView!.comment.path == '0.${commentView.commentView!.comment.id}').toList();
}

List<CommentViewTree> insertNewComment(List<CommentViewTree> comments, CommentView commentView) {
List<String> parentIds = commentView.comment.path.split('.');
String commentTime = commentView.comment.published.toIso8601String();

CommentViewTree newCommentTree = CommentViewTree(
datePostedOrEdited: formatTimeToString(dateTime: commentTime),
commentView: commentView,
replies: [],
level: commentView.comment.path.split('.').length - 2,
);

if (parentIds[1] == commentView.comment.id.toString()) {
comments.insert(0, newCommentTree);
return comments;
}

String parentId = parentIds[parentIds.length - 2];
CommentViewTree? parentComment = findParentComment(1, parentIds, parentId.toString(), comments);

// TODO: surface some sort of error maybe if for some reason we fail to find parent comment
if (parentComment != null) {
parentComment.replies.insert(0, newCommentTree);
}

return comments;
}

CommentViewTree? findParentComment(int index, List<String> parentIds, String targetId, List<CommentViewTree> comments) {
for (CommentViewTree existing in comments) {
if (existing.commentView?.comment.id.toString() != parentIds[index]) {
continue;
}

if (targetId == existing.commentView?.comment.id.toString()) {
return existing;
}

return findParentComment(index + 1, parentIds, targetId, existing.replies);
}

return null;
}

List<int> findCommentIndexesFromCommentViewTree(List<CommentViewTree> commentTrees, int commentId, [List<int>? indexes]) {
indexes ??= [];

Expand Down

0 comments on commit 0e25a8e

Please sign in to comment.