diff --git a/data/src/main/java/com/moez/QKSMS/util/EmojiReactionUtils.kt b/data/src/main/java/com/moez/QKSMS/repository/EmojiReactionRepositoryImpl.kt similarity index 90% rename from data/src/main/java/com/moez/QKSMS/util/EmojiReactionUtils.kt rename to data/src/main/java/com/moez/QKSMS/repository/EmojiReactionRepositoryImpl.kt index 251a69bd..fd2eae4a 100644 --- a/data/src/main/java/com/moez/QKSMS/util/EmojiReactionUtils.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/EmojiReactionRepositoryImpl.kt @@ -16,65 +16,25 @@ * You should have received a copy of the GNU General Public License * along with QUIK. If not, see . */ -package dev.octoshrimpy.quik.util +package dev.octoshrimpy.quik.repository import dev.octoshrimpy.quik.manager.KeyManager import dev.octoshrimpy.quik.model.EmojiReaction import dev.octoshrimpy.quik.model.Message +import dev.octoshrimpy.quik.repository.EmojiReactionUtils.reactionPatterns +import dev.octoshrimpy.quik.repository.EmojiReactionUtils.removalPatterns import io.realm.Realm import io.realm.Sort import timber.log.Timber +import javax.inject.Inject +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.iterator -data class ParsedEmojiReaction(val emoji: String, val originalMessage: String, val isRemoval: Boolean = false) - -object EmojiReactionUtils { - - private fun tapback(emoji: String, isRemoval: Boolean = false): (MatchResult) -> ParsedEmojiReaction { - return { match -> - ParsedEmojiReaction(emoji, match.groupValues[1], isRemoval) - } - } - - val reactionPatterns: Map ParsedEmojiReaction?> = mapOf( - // Google Messages - https://github.com/octoshrimpy/quik/issues/152#issuecomment-2330183516 - Regex("^\u200A\u200B(.+?)\u200B to \u201C\u200A(.+?)\u200A\u201D\u200A$") to { match -> - ParsedEmojiReaction(match.groupValues[1], match.groupValues[2]) - }, - // iOS - Regex("^Reacted (.+?) to \u201C(.+?)\u201D$") to { match -> - if (match.groupValues[1] == "with a sticker") - null - else - ParsedEmojiReaction(match.groupValues[1], match.groupValues[2]) - }, - // iOS tapbacks - Regex("^Loved \u201C(.+?)\u201D$") to tapback("❤️"), - Regex("^Liked \u201C(.+?)\u201D$") to tapback("👍"), - Regex("^Disliked \u201C(.+?)\u201D$") to tapback("👎"), - Regex("^Laughed at \u201C(.+?)\u201D$") to tapback("😂"), - Regex("^Emphasized \u201C(.+?)\u201D$") to tapback("‼️"), - Regex("^Questioned \u201C(.+?)\u201D$") to tapback("❓"), - ) - - val removalPatterns: Map ParsedEmojiReaction?> = mapOf( - // Google Messages - Regex("^\u200ARemoved \u200C(.+?)\u200C from \u201C\u200A(.+?)\u200A\u201D\u200A$") to { match -> - ParsedEmojiReaction(match.groupValues[1], match.groupValues[2], isRemoval = true) - }, - // iOS tapbacks - Regex("^Removed a heart from \u201C(.+?)\u201D$") to tapback("❤️", true), - Regex("^Removed a like from \u201C(.+?)\u201D$") to tapback("👍", true), - Regex("^Removed a dislike from \u201C(.+?)\u201D$") to tapback("👎", true), - Regex("^Removed a laugh from \u201C(.+?)\u201D$") to tapback("😂", true), - Regex("^Removed an exclamation from \u201C(.+?)\u201D$") to tapback("‼️", true), - Regex("^Removed a question mark from \u201C(.+?)\u201D$") to tapback("❓", true), - // iOS emoji - keep this below tapbacks as this regex would otherwise also match the patterns above - Regex("^Removed (.+?) from \u201C(.+?)\u201D$") to { match -> - ParsedEmojiReaction(match.groupValues[1], match.groupValues[2], isRemoval = true) - }, - ) - - fun parseEmojiReaction(body: String): ParsedEmojiReaction? { +class EmojiReactionRepositoryImpl @Inject constructor( + private val keyManager: KeyManager +) : EmojiReactionRepository { + override fun parseEmojiReaction(body: String): ParsedEmojiReaction? { val removal = parseRemoval(body) if (removal != null) return removal @@ -111,7 +71,7 @@ object EmojiReactionUtils { * Search for messages in the same thread with matching text content * We'll search recent messages first (within reasonable time window) */ - fun findTargetMessage(threadId: Long, originalMessageText: String, realm: Realm): Message? { + override fun findTargetMessage(threadId: Long, originalMessageText: String, realm: Realm): Message? { val messages = realm.where(Message::class.java) .equalTo("threadId", threadId) .sort("date", Sort.DESCENDING) @@ -158,11 +118,10 @@ object EmojiReactionUtils { realm.insertOrUpdate(reactionMessage) } - fun saveEmojiReaction( + override fun saveEmojiReaction( reactionMessage: Message, parsedReaction: ParsedEmojiReaction, targetMessage: Message?, - keyManager: KeyManager, realm: Realm, ) { if (parsedReaction.isRemoval) { @@ -191,4 +150,52 @@ object EmojiReactionUtils { Timber.w("Saved emoji reaction without target message: ${reaction.emoji}") } } + +} + +object EmojiReactionUtils { + private fun tapback(emoji: String, isRemoval: Boolean = false): (MatchResult) -> ParsedEmojiReaction { + return { match -> + ParsedEmojiReaction(emoji, match.groupValues[1], isRemoval) + } + } + + val reactionPatterns: Map ParsedEmojiReaction?> = mapOf( + // Google Messages - https://github.com/octoshrimpy/quik/issues/152#issuecomment-2330183516 + Regex("^\u200A\u200B(.+?)\u200B to \u201C\u200A(.+?)\u200A\u201D\u200A$") to { match -> + ParsedEmojiReaction(match.groupValues[1], match.groupValues[2]) + }, + // iOS + Regex("^Reacted (.+?) to \u201C(.+?)\u201D$") to { match -> + if (match.groupValues[1] == "with a sticker") + null + else + ParsedEmojiReaction(match.groupValues[1], match.groupValues[2]) + }, + // iOS tapbacks + Regex("^Loved \u201C(.+?)\u201D$") to tapback("❤️"), + Regex("^Liked \u201C(.+?)\u201D$") to tapback("👍"), + Regex("^Disliked \u201C(.+?)\u201D$") to tapback("👎"), + Regex("^Laughed at \u201C(.+?)\u201D$") to tapback("😂"), + Regex("^Emphasized \u201C(.+?)\u201D$") to tapback("‼️"), + Regex("^Questioned \u201C(.+?)\u201D$") to tapback("❓"), + ) + + val removalPatterns: Map ParsedEmojiReaction?> = mapOf( + // Google Messages + Regex("^\u200ARemoved \u200C(.+?)\u200C from \u201C\u200A(.+?)\u200A\u201D\u200A$") to { match -> + ParsedEmojiReaction(match.groupValues[1], match.groupValues[2], isRemoval = true) + }, + // iOS tapbacks + Regex("^Removed a heart from \u201C(.+?)\u201D$") to tapback("❤️", true), + Regex("^Removed a like from \u201C(.+?)\u201D$") to tapback("👍", true), + Regex("^Removed a dislike from \u201C(.+?)\u201D$") to tapback("👎", true), + Regex("^Removed a laugh from \u201C(.+?)\u201D$") to tapback("😂", true), + Regex("^Removed an exclamation from \u201C(.+?)\u201D$") to tapback("‼️", true), + Regex("^Removed a question mark from \u201C(.+?)\u201D$") to tapback("❓", true), + // iOS emoji - keep this below tapbacks as this regex would otherwise also match the patterns above + Regex("^Removed (.+?) from \u201C(.+?)\u201D$") to { match -> + ParsedEmojiReaction(match.groupValues[1], match.groupValues[2], isRemoval = true) + }, + ) } diff --git a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt index 49d386ee..068b579d 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt @@ -57,7 +57,6 @@ import dev.octoshrimpy.quik.receiver.SendSmsReceiver import dev.octoshrimpy.quik.receiver.SmsDeliveredReceiver import dev.octoshrimpy.quik.receiver.SmsSentReceiver import dev.octoshrimpy.quik.util.ImageUtils -import dev.octoshrimpy.quik.util.EmojiReactionUtils import dev.octoshrimpy.quik.util.PhoneNumberUtils import dev.octoshrimpy.quik.util.Preferences import dev.octoshrimpy.quik.util.tryOrNull @@ -79,6 +78,7 @@ open class MessageRepositoryImpl @Inject constructor( private val phoneNumberUtils: PhoneNumberUtils, private val prefs: Preferences, private val syncRepository: SyncRepository, + private val reactions: EmojiReactionRepository, ) : MessageRepository { companion object { @@ -765,19 +765,18 @@ open class MessageRepositoryImpl @Inject constructor( } managedMessage?.let { savedMessage -> - val parsedReaction = EmojiReactionUtils.parseEmojiReaction(body) + val parsedReaction = reactions.parseEmojiReaction(body) if (parsedReaction != null) { - val targetMessage = EmojiReactionUtils.findTargetMessage( + val targetMessage = reactions.findTargetMessage( savedMessage.threadId, parsedReaction.originalMessage, realm ) realm.executeTransaction { - EmojiReactionUtils.saveEmojiReaction( + reactions.saveEmojiReaction( savedMessage, parsedReaction, targetMessage, - messageIds, realm, ) } diff --git a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt index a7e8fe9f..5241c957 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt @@ -45,7 +45,6 @@ import dev.octoshrimpy.quik.model.MmsPart import dev.octoshrimpy.quik.model.PhoneNumber import dev.octoshrimpy.quik.model.Recipient import dev.octoshrimpy.quik.model.SyncLog -import dev.octoshrimpy.quik.util.EmojiReactionUtils import dev.octoshrimpy.quik.util.PhoneNumberUtils import dev.octoshrimpy.quik.util.tryOrNull import io.reactivex.subjects.BehaviorSubject @@ -70,7 +69,8 @@ class SyncRepositoryImpl @Inject constructor( private val cursorToContactGroupMember: CursorToContactGroupMember, private val keys: KeyManager, private val phoneNumberUtils: PhoneNumberUtils, - private val rxPrefs: RxSharedPreferences + private val rxPrefs: RxSharedPreferences, + private val reactions: EmojiReactionRepository, ) : SyncRepository { override val syncProgress: Subject = @@ -248,18 +248,17 @@ class SyncRepositoryImpl @Inject constructor( allMessages.forEach { message -> val text = message.getText(false) - val parsedReaction = EmojiReactionUtils.parseEmojiReaction(text) + val parsedReaction = reactions.parseEmojiReaction(text) if (parsedReaction != null) { - val targetMessage = EmojiReactionUtils.findTargetMessage( + val targetMessage = reactions.findTargetMessage( message.threadId, parsedReaction.originalMessage, realm ) - EmojiReactionUtils.saveEmojiReaction( + reactions.saveEmojiReaction( message, parsedReaction, targetMessage, - keys, realm, ) } @@ -326,20 +325,19 @@ class SyncRepositoryImpl @Inject constructor( insertOrUpdate() val text = getText(false) - val parsedReaction = EmojiReactionUtils.parseEmojiReaction(text) + val parsedReaction = reactions.parseEmojiReaction(text) if (parsedReaction != null) { Realm.getDefaultInstance().use { realm -> - val targetMessage = EmojiReactionUtils.findTargetMessage( + val targetMessage = reactions.findTargetMessage( threadId, parsedReaction.originalMessage, realm ) realm.executeTransaction { - EmojiReactionUtils.saveEmojiReaction( + reactions.saveEmojiReaction( this, parsedReaction, targetMessage, - keys, realm, ) } diff --git a/domain/src/main/java/com/moez/QKSMS/repository/EmojiReactionRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/EmojiReactionRepository.kt new file mode 100644 index 00000000..d327863c --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/repository/EmojiReactionRepository.kt @@ -0,0 +1,19 @@ +package dev.octoshrimpy.quik.repository + +import dev.octoshrimpy.quik.model.Message +import io.realm.Realm + +data class ParsedEmojiReaction(val emoji: String, val originalMessage: String, val isRemoval: Boolean = false) + +interface EmojiReactionRepository { + fun parseEmojiReaction(body: String): ParsedEmojiReaction? + + fun findTargetMessage(threadId: Long, originalMessageText: String, realm: Realm): Message? + + fun saveEmojiReaction( + reactionMessage: Message, + parsedReaction: ParsedEmojiReaction, + targetMessage: Message?, + realm: Realm, + ) +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt index 8a1d35b2..8d52fe2f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt @@ -81,6 +81,8 @@ import dev.octoshrimpy.quik.repository.ContactRepository import dev.octoshrimpy.quik.repository.ContactRepositoryImpl import dev.octoshrimpy.quik.repository.ConversationRepository import dev.octoshrimpy.quik.repository.ConversationRepositoryImpl +import dev.octoshrimpy.quik.repository.EmojiReactionRepository +import dev.octoshrimpy.quik.repository.EmojiReactionRepositoryImpl import dev.octoshrimpy.quik.repository.MessageContentFilterRepository import dev.octoshrimpy.quik.repository.MessageContentFilterRepositoryImpl import dev.octoshrimpy.quik.repository.MessageRepository @@ -219,6 +221,9 @@ class AppModule(private var application: Application) { @Provides fun provideSyncRepository(repository: SyncRepositoryImpl): SyncRepository = repository + @Provides + fun provideEmojiReactionRepository(repository: EmojiReactionRepositoryImpl): EmojiReactionRepository = repository + // worker factory @Provides fun provideWorkerFactory(workerFactory: InjectionWorkerFactory): WorkerFactory = workerFactory