From 9f3be64a675f7f70076fb5e583b231e4191f1f3f Mon Sep 17 00:00:00 2001 From: Vincent TE Date: Thu, 11 Jul 2024 14:00:15 +0200 Subject: [PATCH 1/2] Fix a crash on API25 when using the get method on MatchResult --- .../ui/newMessage/AiPropositionFragment.kt | 16 ++-------- .../mail/ui/newMessage/AiViewModel.kt | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiPropositionFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiPropositionFragment.kt index 8e54a37b46..3655fe5430 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiPropositionFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiPropositionFragment.kt @@ -88,7 +88,7 @@ class AiPropositionFragment : Fragment() { return FragmentAiPropositionBinding.inflate(inflater, container, false).also { _binding = it }.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) = with(binding) { + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setSystemBarsColors(statusBarColor = R.color.backgroundColor) @@ -175,7 +175,7 @@ class AiPropositionFragment : Fragment() { findNavController().popBackStack() } - val (subject, content) = splitBodyAndSubject(getLastMessage()) + val (subject, content) = aiViewModel.splitBodyAndSubject(getLastMessage()) if (subject == null || navigationArgs.isSubjectBlank) { applyProposition(subject, content) @@ -208,17 +208,6 @@ class AiPropositionFragment : Fragment() { } } - private fun splitBodyAndSubject(proposition: String): Pair { - val match = MATCH_SUBJECT_REGEX.find(proposition) - - val content = match?.groups?.get("content")?.value ?: return null to proposition - val subject = match.groups["subject"]?.value?.trim() - - if (subject.isNullOrBlank()) return null to proposition - - return subject to content - } - private fun onMenuItemClicked(menuItemId: Int) = with(aiViewModel) { val shortcut = Shortcut.entries.find { it.menuId == menuItemId }!! trackAiWriterEvent(shortcut.matomoValue) @@ -381,6 +370,5 @@ class AiPropositionFragment : Fragment() { companion object { private const val REPLACEMENT_DURATION = 150L - private val MATCH_SUBJECT_REGEX = Regex("^[^:]+:(?.+?)\\n\\s*(?.+)", RegexOption.DOT_MATCHES_ALL) } } diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiViewModel.kt index e1c08edcb0..b391404620 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiViewModel.kt @@ -17,6 +17,7 @@ */ package com.infomaniak.mail.ui.newMessage +import android.os.Build import androidx.annotation.IdRes import androidx.annotation.StringRes import androidx.lifecycle.MutableLiveData @@ -89,6 +90,30 @@ class AiViewModel @Inject constructor( handleAiResult(apiResponse, userMessage, isUsingPreviousMessageAsContext = previousMessageBodyPlainText != null) } + fun splitBodyAndSubject(proposition: String): Pair { + return getSubjectAndContent(MATCH_SUBJECT_REGEX.find(proposition), proposition) + } + + private fun getSubjectAndContent(match: MatchResult?, proposition: String): Pair { + val (subject, content) = match?.let { + // The method get on MatchGroupCollection is not available on API25 + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) { + val destructuredList = it.destructured.toList() + destructuredList[INDEX_AI_PROPOSITION_SUBJECT] to destructuredList[INDEX_AI_PROPOSITION_CONTENT].trim() + } else { + val subject = it.groups["subject"]?.value?.trim() + val content = it.groups["content"]?.value + subject to content + } + } ?: (null to proposition) + + return if (subject.isNullOrBlank() || content == null) { + null to proposition + } else { + subject to content + } + } + private fun handleAiResult( apiResponse: ApiResponse, promptMessage: AiMessage?, @@ -162,4 +187,10 @@ class AiViewModel @Inject constructor( RATE_LIMIT_EXCEEDED(R.string.aiErrorTooManyRequests), MISSING_CONTENT(R.string.aiErrorUnknown), } + + companion object { + private val MATCH_SUBJECT_REGEX = Regex("^[^:]+:(?.+?)\\n\\s*(?.+)", RegexOption.DOT_MATCHES_ALL) + private val INDEX_AI_PROPOSITION_SUBJECT = 0 + private val INDEX_AI_PROPOSITION_CONTENT = 1 + } } From f926bb9adf9358bedf82023403d5af893c2cdf93 Mon Sep 17 00:00:00 2001 From: Vincent TE Date: Thu, 11 Jul 2024 14:46:57 +0200 Subject: [PATCH 2/2] Code review --- .../mail/ui/newMessage/AiViewModel.kt | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiViewModel.kt index b391404620..7b3664a0ce 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiViewModel.kt @@ -19,6 +19,7 @@ package com.infomaniak.mail.ui.newMessage import android.os.Build import androidx.annotation.IdRes +import androidx.annotation.RequiresApi import androidx.annotation.StringRes import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -91,29 +92,35 @@ class AiViewModel @Inject constructor( } fun splitBodyAndSubject(proposition: String): Pair { - return getSubjectAndContent(MATCH_SUBJECT_REGEX.find(proposition), proposition) - } - - private fun getSubjectAndContent(match: MatchResult?, proposition: String): Pair { - val (subject, content) = match?.let { - // The method get on MatchGroupCollection is not available on API25 - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) { - val destructuredList = it.destructured.toList() - destructuredList[INDEX_AI_PROPOSITION_SUBJECT] to destructuredList[INDEX_AI_PROPOSITION_CONTENT].trim() - } else { - val subject = it.groups["subject"]?.value?.trim() - val content = it.groups["content"]?.value - subject to content - } - } ?: (null to proposition) - - return if (subject.isNullOrBlank() || content == null) { - null to proposition + val match = MATCH_SUBJECT_REGEX.find(proposition) + // The method get on MatchGroupCollection is not available on API25 + return if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) { + splitBodyAndSubjectForAPI25(match, proposition) } else { - subject to content + splitBodyAndSubjectAfterAPI25(match, proposition) } } + private fun splitBodyAndSubjectForAPI25(match: MatchResult?, proposition: String): Pair { + val destructuredList = match?.destructured?.toList() + val content = destructuredList?.getOrNull(INDEX_AI_PROPOSITION_CONTENT) ?: return null to proposition + val subject = destructuredList.getOrNull(INDEX_AI_PROPOSITION_SUBJECT)?.trim() + + if (subject.isNullOrBlank()) return null to proposition + + return subject to content + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun splitBodyAndSubjectAfterAPI25(match: MatchResult?, proposition: String): Pair { + val content = match?.groups?.get("content")?.value ?: return null to proposition + val subject = match.groups["subject"]?.value?.trim() + + if (subject.isNullOrBlank()) return null to proposition + + return subject to content + } + private fun handleAiResult( apiResponse: ApiResponse, promptMessage: AiMessage?,