import 'package:cached_network_image/cached_network_image.dart'; import 'package:material_ui/material_ui.dart'; import 'package:get_it/get_it.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:spark/src/core/network/atproto/atproto.dart'; import 'package:spark/src/core/network/atproto/data/models/feed_models.dart'; import 'package:spark/src/features/comments/providers/comment_state.dart'; import 'package:spark/src/features/feed/providers/post_updates.dart'; part 'comment_provider.g.dart'; @Riverpod(keepAlive: true) class CommentNotifier extends _$CommentNotifier { FeedRepository get _feedRepository => GetIt.instance().feed; @override CommentState build(Thread thread) { switch (thread) { case ThreadViewPost(): return CommentState(thread: thread); case NotFoundPost(): throw Exception('Post not found'); case BlockedPost(): throw Exception('Post is blocked'); } } Future toggleLike() async { final wasLiked = state.isLiked; final currentLikeCount = state.thread.post.likeCount ?? 0; final postUri = state.thread.post.uri.toString(); final originalState = state; try { if (wasLiked) { final likeUri = state.thread.post.viewer!.like!; final updatedPost = switch (state.thread.post) { ThreadPostView(:final post) => ThreadPostView( post: post.copyWith( viewer: post.viewer?.copyWith(like: null), likeCount: currentLikeCount - 1, ), ), ThreadReplyView(:final reply) => ThreadReplyView( reply: ReplyView( uri: reply.uri, cid: reply.cid, author: reply.author, record: reply.record, indexedAt: reply.indexedAt, media: reply.media, replyCount: reply.replyCount, labels: reply.labels, viewer: reply.viewer?.copyWith(like: null), likeCount: currentLikeCount - 1, ), ), }; state = state.copyWith( thread: _withUpdatedPost(state.thread, updatedPost), ); await _feedRepository.unlikePost(likeUri); ref.read(postUpdateProvider(postUri).notifier).state++; } else { final response = await _feedRepository.likePost( state.thread.post.cid, state.thread.post.uri, ); final updatedPost = switch (state.thread.post) { ThreadPostView(:final post) => ThreadPostView( post: post.copyWith( viewer: post.viewer?.copyWith(like: response.uri) ?? ViewerState(like: response.uri), likeCount: currentLikeCount + 1, ), ), ThreadReplyView(:final reply) => ThreadReplyView( reply: ReplyView( uri: reply.uri, cid: reply.cid, author: reply.author, record: reply.record, indexedAt: reply.indexedAt, media: reply.media, replyCount: reply.replyCount, labels: reply.labels, viewer: reply.viewer?.copyWith(like: response.uri) ?? ReplyViewerState(like: response.uri), likeCount: currentLikeCount + 1, ), ), }; state = state.copyWith( thread: _withUpdatedPost(state.thread, updatedPost), ); ref.read(postUpdateProvider(postUri).notifier).state++; } } catch (e) { state = originalState; rethrow; } } void preloadFirstImage(BuildContext context) { final imageUrls = state.thread.post.imageUrls; if (imageUrls.isNotEmpty) { precacheImage(CachedNetworkImageProvider(imageUrls.first), context); } } ThreadViewPost _withUpdatedPost(ThreadViewPost thread, ThreadPost post) { return Thread.threadViewPost( post: post, parent: thread.parent, replies: thread.replies, context: thread.context, ) as ThreadViewPost; } }