diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 5c5f85920d..30661eb9cc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -1771,11 +1771,23 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, return } else { // Put the message in the database + val messageId = originalMessage.messageId + val count = if (recipient.isCommunityRecipient) { + // ReactionRecord count is total number of reactions for this emoji/messageId in community, + // regardless of the author of each ReactionRecord. + // We set to the existing number for now and we'll increase all of them + // by 1 below. + originalMessage.reactions.firstOrNull { it.messageId == messageId && it.emoji == emoji } + ?.count ?: 0 + } else { + 1 + } + val reaction = ReactionRecord( - messageId = originalMessage.messageId, + messageId = messageId, author = author, emoji = emoji, - count = 1, + count = count, dateSent = emojiTimestamp, dateReceived = emojiTimestamp ) @@ -1789,7 +1801,11 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, reactionMessage.reaction = Reaction.from(originalMessage.timestamp, originalAuthor.toString(), emoji, true) if (recipient.address is Address.Community) { - val messageServerId = lokiMessageDb.getServerID(originalMessage.messageId) ?: + // Increment the reaction count locally immediately. This + // has to apply on all the ReactionRecords with the same messageId/emoji per design. + reactionDb.updateAllCountFor(messageId, emoji, 1) + + val messageServerId = lokiMessageDb.getServerID(messageId) ?: return Log.w(TAG, "Failed to find message server ID when adding emoji reaction") scope.launch { @@ -1825,7 +1841,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, } else { reactionDb.deleteReaction( emoji, - MessageId(originalMessage.id, originalMessage.isMms), + originalMessage.messageId, author ) @@ -1841,6 +1857,10 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, ) if (recipient.address is Address.Community) { + // Decrement the reaction count locally immediately (they will + // get overwritten when we get the server update back) + reactionDb.updateAllCountFor(originalMessage.messageId, emoji, -1) + val messageServerId = lokiMessageDb.getServerID(originalMessage.messageId) ?: return Log.w(TAG, "Failed to find message server ID when removing emoji reaction") @@ -2025,7 +2045,13 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, override fun onReactionLongClicked(messageId: MessageId, emoji: String?) { if (viewModel.recipient.isGroupOrCommunityRecipient) { val isUserCommunityModerator = viewModel.recipient.takeIf { it.isCommunityRecipient }?.currentUserRole?.canModerate == true - val fragment = ReactionsDialogFragment.create(messageId, isUserCommunityModerator, emoji, viewModel.canRemoveReaction) + val fragment = ReactionsDialogFragment.create( + messageId, + isUserCommunityModerator, + emoji, + viewModel.canRemoveReaction, + viewModel.recipient.isCommunityRecipient + ) fragment.show(supportFragmentManager, TAG_REACTION_FRAGMENT) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt index 8b47b7dfb0..9ed01cc896 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt @@ -264,6 +264,30 @@ class ReactionDatabase(context: Context, helper: Provider) ) } + /** + * Update the count for all reactions with the given emoji on the specified message. + * Note this should ONLY be used on community reactions as each reaction record contains the + * same count for that particular emoji/messageId, so it makes sense to update them all at once. + * + * For other type of messages, this will likely result to errors in the reaction counts! + */ + fun updateAllCountFor(messageId: MessageId, emoji: String, countDiff: Int) { + val changed = writableDatabase.compileStatement(""" + UPDATE $TABLE_NAME SET $COUNT = $COUNT + ? + WHERE $MESSAGE_ID = ? AND $IS_MMS = ? AND $EMOJI = ? + """).use { statement -> + statement.bindLong(1, countDiff.toLong()) + statement.bindLong(2, messageId.id) + statement.bindLong(3, if (messageId.mms) 1 else 0) + statement.bindString(4, emoji) + statement.executeUpdateDelete() > 0 + } + + if (changed) { + mutableChangeNotification.tryEmit(messageId) + } + } + private fun deleteReactions(query: String, args: Array) { val updatedMessageIDs = writableDatabase.rawQuery("DELETE FROM $TABLE_NAME WHERE $query RETURNING $MESSAGE_ID, $IS_MMS", *args).use { cursor -> cursor.asSequence() diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/EmojiCount.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/EmojiCount.java index 46030f7504..566ed85f8f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/EmojiCount.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/EmojiCount.java @@ -6,23 +6,24 @@ import java.util.List; -final class EmojiCount { +import kotlin.collections.CollectionsKt; - static EmojiCount all(@NonNull List reactions) { - return new EmojiCount("", "", reactions); - } +public final class EmojiCount { private final String baseEmoji; private final String displayEmoji; private final List reactions; + private final boolean shouldAccumulateReactionCount; EmojiCount(@NonNull String baseEmoji, @NonNull String emoji, - @NonNull List reactions) + @NonNull List reactions, + boolean shouldAccumulateReactionCount) { this.baseEmoji = baseEmoji; this.displayEmoji = emoji; this.reactions = reactions; + this.shouldAccumulateReactionCount = shouldAccumulateReactionCount; } public @NonNull String getBaseEmoji() { @@ -34,7 +35,12 @@ static EmojiCount all(@NonNull List reactions) { } public int getCount() { - return Stream.of(reactions).reduce(0, (count, reaction) -> count + reaction.getCount()); + if (shouldAccumulateReactionCount) { + return CollectionsKt.fold(reactions, 0, (count, reaction) -> count + reaction.getCount()); + } + + ReactionDetails first = CollectionsKt.getOrNull(reactions, 0); + return first == null ? 0 : first.getCount(); } public @NonNull List getReactions() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDialogFragment.java index 4fc6fdbe98..a8f4106bbb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDialogFragment.java @@ -43,6 +43,7 @@ public final class ReactionsDialogFragment extends BottomSheetDialogFragment imp private static final String ARGS_IS_MODERATOR = "reactions.args.is.moderator"; private static final String ARGS_EMOJI = "reactions.args.emoji"; private static final String ARGS_CAN_REMOVE = "reactions.args.can.remove"; + private static final String ARGS_FROM_COMMUNITY_THREAD = "reactions.args.from.community.thread"; private ViewPager2 recipientPagerView; private ReactionViewPagerAdapter recipientsAdapter; @@ -53,7 +54,11 @@ public final class ReactionsDialogFragment extends BottomSheetDialogFragment imp private final LifecycleDisposable disposables = new LifecycleDisposable(); - public static DialogFragment create(MessageId messageId, boolean isUserModerator, @Nullable String emoji, boolean canRemove) { + public static DialogFragment create(MessageId messageId, + boolean isUserModerator, + @Nullable String emoji, + boolean canRemove, + boolean fromCommunityThread) { Bundle args = new Bundle(); DialogFragment fragment = new ReactionsDialogFragment(); @@ -62,6 +67,7 @@ public static DialogFragment create(MessageId messageId, boolean isUserModerator args.putBoolean(ARGS_IS_MODERATOR, isUserModerator); args.putString(ARGS_EMOJI, emoji); args.putBoolean(ARGS_CAN_REMOVE, canRemove); + args.putBoolean(ARGS_FROM_COMMUNITY_THREAD, fromCommunityThread); fragment.setArguments(args); @@ -158,7 +164,8 @@ private void setUpViewModel(@NonNull MessageId messageId) { getDefaultViewModelProviderFactory(), HiltViewModelExtensions.withCreationCallback( getDefaultViewModelCreationExtras(), - (ReactionsViewModel.Factory factory) -> factory.create(messageId) + (ReactionsViewModel.Factory factory) -> factory.create( + messageId, requireArguments().getBoolean(ARGS_FROM_COMMUNITY_THREAD)) ) ).get(ReactionsViewModel.class); diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java index 0c5c601f77..9d85a0f2bd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java @@ -25,11 +25,15 @@ public class ReactionsViewModel extends ViewModel { private final MessageId messageId; private final ReactionsRepository repository; + private final boolean fromCommunityThread; @AssistedInject - public ReactionsViewModel(@Assisted @NonNull MessageId messageId, final ReactionsRepository repository) { + public ReactionsViewModel(@Assisted @NonNull MessageId messageId, + @Assisted boolean fromCommunityThread, + final ReactionsRepository repository) { this.messageId = messageId; this.repository = repository; + this.fromCommunityThread = fromCommunityThread; } public @NonNull @@ -38,9 +42,11 @@ Observable> getEmojiCounts() { .map(reactionList -> Stream.of(reactionList) .groupBy(ReactionDetails::getBaseEmoji) .sorted(this::compareReactions) - .map(entry -> new EmojiCount(entry.getKey(), - getCountDisplayEmoji(entry.getValue()), - entry.getValue())) + .map(entry -> new EmojiCount( + entry.getKey(), + getCountDisplayEmoji(entry.getValue()), + entry.getValue(), + !fromCommunityThread)) .toList()) .observeOn(AndroidSchedulers.mainThread()); } @@ -75,6 +81,6 @@ private long getLatestTimestamp(List reactions) { @AssistedFactory public interface Factory { - ReactionsViewModel create(@NonNull MessageId messageId); + ReactionsViewModel create(@NonNull MessageId messageId, boolean fromCommunityThread); } }