diff --git a/app/src/androidTest/kotlin/com/android/messaging/ui/appsettings/general/ui/AppSettingsScreenTest.kt b/app/src/androidTest/kotlin/com/android/messaging/ui/appsettings/general/ui/AppSettingsScreenTest.kt index 82c919ee1..d37a98ee4 100644 --- a/app/src/androidTest/kotlin/com/android/messaging/ui/appsettings/general/ui/AppSettingsScreenTest.kt +++ b/app/src/androidTest/kotlin/com/android/messaging/ui/appsettings/general/ui/AppSettingsScreenTest.kt @@ -180,6 +180,7 @@ class AppSettingsScreenTest { appSettings = AppSettingsUiState(), onAction = screenModel::onAction, onNavigateBack = {}, + onPrivacyClick = {}, isTopLevel = true, onAdvancedClick = { advancedClicks += 1 }, ) @@ -203,6 +204,7 @@ class AppSettingsScreenTest { appSettings = AppSettingsUiState(), onAction = screenModel::onAction, onNavigateBack = {}, + onPrivacyClick = {}, isTopLevel = false, onAdvancedClick = null, ) @@ -226,6 +228,7 @@ class AppSettingsScreenTest { appSettings = appSettings, onAction = screenModel::onAction, onNavigateBack = {}, + onPrivacyClick = {}, ) } } diff --git a/app/src/androidTest/kotlin/com/android/messaging/ui/appsettings/privacy/ui/PrivacySettingsScreenTest.kt b/app/src/androidTest/kotlin/com/android/messaging/ui/appsettings/privacy/ui/PrivacySettingsScreenTest.kt new file mode 100644 index 000000000..b3a063d14 --- /dev/null +++ b/app/src/androidTest/kotlin/com/android/messaging/ui/appsettings/privacy/ui/PrivacySettingsScreenTest.kt @@ -0,0 +1,91 @@ +package com.android.messaging.ui.appsettings.privacy.ui + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsOff +import androidx.compose.ui.test.isToggleable +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import com.android.messaging.R +import com.android.messaging.ui.appsettings.general.model.AppSettingsUiState +import com.android.messaging.ui.appsettings.screen.SettingsScreenModel +import com.android.messaging.ui.appsettings.screen.model.SettingsAction as Action +import com.android.messaging.ui.core.AppTheme +import io.mockk.mockk +import io.mockk.verify +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class PrivacySettingsScreenTest { + + @get:Rule + val composeTestRule = createAndroidComposeRule() + + private lateinit var screenModel: SettingsScreenModel + + @Before + fun setup() { + screenModel = mockk(relaxed = true) + } + + @Test + fun youTubeLinkPreviewsToggle_displaysTitleSummaryAndDefaultsToOff() { + setContent() + + val title = composeTestRule.activity.getString( + R.string.youtube_link_previews_pref_title, + ) + val summary = composeTestRule.activity.getString( + R.string.youtube_link_previews_pref_summary, + ) + composeTestRule.onNodeWithText(title).assertIsDisplayed() + composeTestRule.onNodeWithText(summary).assertIsDisplayed() + composeTestRule.onNode(isToggleable()).assertIsOff() + } + + @Test + fun youTubeLinkPreviewsToggle_delegatesToScreenModel() { + setContent() + + val title = composeTestRule.activity.getString( + R.string.youtube_link_previews_pref_title, + ) + composeTestRule.onNodeWithText(title).performClick() + + verify(exactly = 1) { + screenModel.onAction(Action.YouTubeLinkPreviewsChanged(true)) + } + } + + @Test + fun youTubeLinkPreviewsToggle_whenEnabled_delegatesDisable() { + setContent( + appSettings = AppSettingsUiState(youTubeLinkPreviewsEnabled = true), + ) + + val title = composeTestRule.activity.getString( + R.string.youtube_link_previews_pref_title, + ) + composeTestRule.onNodeWithText(title).performClick() + + verify(exactly = 1) { + screenModel.onAction(Action.YouTubeLinkPreviewsChanged(false)) + } + } + + private fun setContent( + appSettings: AppSettingsUiState = AppSettingsUiState(), + ) { + composeTestRule.setContent { + AppTheme { + PrivacySettingsScreen( + appSettings = appSettings, + onAction = screenModel::onAction, + onNavigateBack = {}, + ) + } + } + } +} diff --git a/app/src/androidTest/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageLinkLongClickTest.kt b/app/src/androidTest/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageLinkLongClickTest.kt index 6cf13bfcd..7a8a8db81 100644 --- a/app/src/androidTest/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageLinkLongClickTest.kt +++ b/app/src/androidTest/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageLinkLongClickTest.kt @@ -255,6 +255,7 @@ class ConversationMessageLinkLongClickTest { canForwardMessage = true, canResendMessage = false, canSaveAttachments = false, + isYouTubePreviewEnabled = false, mmsDownload = null, mmsSubject = null, protocol = ConversationMessageUiModel.Protocol.SMS, diff --git a/app/src/sharedTest/kotlin/com/android/messaging/ui/conversation/messages/ui/message/rendering/BaseConversationMessageRenderingTest.kt b/app/src/sharedTest/kotlin/com/android/messaging/ui/conversation/messages/ui/message/rendering/BaseConversationMessageRenderingTest.kt index dcc46cc37..dc3617c6e 100644 --- a/app/src/sharedTest/kotlin/com/android/messaging/ui/conversation/messages/ui/message/rendering/BaseConversationMessageRenderingTest.kt +++ b/app/src/sharedTest/kotlin/com/android/messaging/ui/conversation/messages/ui/message/rendering/BaseConversationMessageRenderingTest.kt @@ -152,6 +152,7 @@ internal abstract class BaseConversationMessageRenderingTest { canForwardMessage = true, canResendMessage = canResendMessage, canSaveAttachments = canSaveAttachments, + isYouTubePreviewEnabled = false, mmsDownload = mmsDownload, mmsSubject = mmsSubject, protocol = protocol, diff --git a/app/src/sharedTest/kotlin/com/android/messaging/ui/conversation/screen/BaseConversationScreenTest.kt b/app/src/sharedTest/kotlin/com/android/messaging/ui/conversation/screen/BaseConversationScreenTest.kt index 683a6eb58..0d571b481 100644 --- a/app/src/sharedTest/kotlin/com/android/messaging/ui/conversation/screen/BaseConversationScreenTest.kt +++ b/app/src/sharedTest/kotlin/com/android/messaging/ui/conversation/screen/BaseConversationScreenTest.kt @@ -174,6 +174,7 @@ internal abstract class BaseConversationScreenTest { canForwardMessage = true, canResendMessage = false, canSaveAttachments = false, + isYouTubePreviewEnabled = false, mmsDownload = null, mmsSubject = null, protocol = ConversationMessageUiModel.Protocol.SMS, diff --git a/app/src/test/kotlin/com/android/messaging/data/appsettings/repository/appsettingsrepository/AppSettingsRepositoryImplTest.kt b/app/src/test/kotlin/com/android/messaging/data/appsettings/repository/appsettingsrepository/AppSettingsRepositoryImplTest.kt index 550fd94cf..b1d126cc0 100644 --- a/app/src/test/kotlin/com/android/messaging/data/appsettings/repository/appsettingsrepository/AppSettingsRepositoryImplTest.kt +++ b/app/src/test/kotlin/com/android/messaging/data/appsettings/repository/appsettingsrepository/AppSettingsRepositoryImplTest.kt @@ -30,6 +30,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment @OptIn(ExperimentalCoroutinesApi::class) @RunWith(RobolectricTestRunner::class) @@ -63,6 +64,12 @@ internal class AppSettingsRepositoryImplTest { every { context.getString(R.string.send_sound_pref_key) } returns SEND_SOUND_PREF_KEY every { context.getString(R.string.dump_sms_pref_key) } returns DUMP_SMS_PREF_KEY every { context.getString(R.string.dump_mms_pref_key) } returns DUMP_MMS_PREF_KEY + every { + context.getString(R.string.youtube_link_previews_pref_key) + } returns YOUTUBE_LINK_PREVIEWS_PREF_KEY + every { + resources.getBoolean(R.bool.youtube_link_previews_pref_default) + } returns YOUTUBE_LINK_PREVIEWS_DEFAULT every { resources.getBoolean(R.bool.send_sound_pref_default) } returns SEND_SOUND_DEFAULT every { resources.getBoolean(R.bool.dump_sms_pref_default) } returns DUMP_SMS_DEFAULT @@ -83,6 +90,12 @@ internal class AppSettingsRepositoryImplTest { every { appPrefs.getBoolean(SEND_SOUND_PREF_KEY, SEND_SOUND_DEFAULT) } returns false every { appPrefs.getBoolean(DUMP_SMS_PREF_KEY, DUMP_SMS_DEFAULT) } returns true every { appPrefs.getBoolean(DUMP_MMS_PREF_KEY, DUMP_MMS_DEFAULT) } returns false + every { + appPrefs.getBoolean( + YOUTUBE_LINK_PREVIEWS_PREF_KEY, + YOUTUBE_LINK_PREVIEWS_DEFAULT, + ) + } returns true val result = createRepository( ioDispatcher = UnconfinedTestDispatcher(testScheduler), @@ -91,11 +104,16 @@ internal class AppSettingsRepositoryImplTest { assertTrue(result.isDefaultSmsApp) assertEquals(DEFAULT_SMS_APP_LABEL, result.defaultSmsAppLabel) assertFalse(result.sendSoundEnabled) + assertTrue(result.youTubeLinkPreviewsEnabled) assertTrue(result.isDebugEnabled) assertTrue(result.dumpSmsEnabled) assertFalse(result.dumpMmsEnabled) verify(exactly = 1) { appPrefs.getBoolean(SEND_SOUND_PREF_KEY, SEND_SOUND_DEFAULT) + appPrefs.getBoolean( + YOUTUBE_LINK_PREVIEWS_PREF_KEY, + YOUTUBE_LINK_PREVIEWS_DEFAULT, + ) appPrefs.getBoolean(DUMP_SMS_PREF_KEY, DUMP_SMS_DEFAULT) appPrefs.getBoolean(DUMP_MMS_PREF_KEY, DUMP_MMS_DEFAULT) } @@ -122,6 +140,10 @@ internal class AppSettingsRepositoryImplTest { pref = AppBooleanPref.DUMP_MMS, enabled = true, ) + repository.setBooleanPref( + pref = AppBooleanPref.YOUTUBE_LINK_PREVIEWS, + enabled = true, + ) verify(exactly = 1) { appPrefs.putBoolean( @@ -136,10 +158,47 @@ internal class AppSettingsRepositoryImplTest { DUMP_MMS_PREF_KEY, true, ) + appPrefs.putBoolean( + YOUTUBE_LINK_PREVIEWS_PREF_KEY, + true, + ) } } } + @Test + fun isYouTubeLinkPreviewsEnabled_readsPreferenceWithResourceDefault() { + runTest { + every { + appPrefs.getBoolean( + YOUTUBE_LINK_PREVIEWS_PREF_KEY, + YOUTUBE_LINK_PREVIEWS_DEFAULT, + ) + } returns true + + val result = createRepository( + ioDispatcher = UnconfinedTestDispatcher(testScheduler), + ).isYouTubeLinkPreviewsEnabled() + + assertTrue(result) + } + } + + @Test + fun isYouTubeLinkPreviewsEnabled_defaultsToDisabledFromResources() { + runTest { + every { appPrefs.getBoolean(any(), any()) } answers { secondArg() } + + val result = AppSettingsRepositoryImpl( + context = RuntimeEnvironment.getApplication().applicationContext, + ioDispatcher = UnconfinedTestDispatcher(testScheduler), + debugFeaturesProvider = debugFeaturesProvider, + ).isYouTubeLinkPreviewsEnabled() + + assertFalse(result) + } + } + private fun createRepository(ioDispatcher: CoroutineDispatcher): AppSettingsRepositoryImpl { return AppSettingsRepositoryImpl( context = context, @@ -156,5 +215,7 @@ internal class AppSettingsRepositoryImplTest { private const val DUMP_SMS_PREF_KEY = "dump_sms" private const val SEND_SOUND_DEFAULT = true private const val SEND_SOUND_PREF_KEY = "send_sound" + private const val YOUTUBE_LINK_PREVIEWS_DEFAULT = false + private const val YOUTUBE_LINK_PREVIEWS_PREF_KEY = "youtube_link_previews" } } diff --git a/app/src/test/kotlin/com/android/messaging/ui/appsettings/general/mapper/appsettingsuistatemapper/AppSettingsUiStateMapperImplTest.kt b/app/src/test/kotlin/com/android/messaging/ui/appsettings/general/mapper/appsettingsuistatemapper/AppSettingsUiStateMapperImplTest.kt index 926d8788e..5dc90a286 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/appsettings/general/mapper/appsettingsuistatemapper/AppSettingsUiStateMapperImplTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/appsettings/general/mapper/appsettingsuistatemapper/AppSettingsUiStateMapperImplTest.kt @@ -28,6 +28,7 @@ internal class AppSettingsUiStateMapperImplTest { isDefaultSmsApp = true, defaultSmsAppLabel = DEFAULT_SMS_APP_LABEL, sendSoundEnabled = false, + youTubeLinkPreviewsEnabled = true, isDebugEnabled = true, dumpSmsEnabled = true, dumpMmsEnabled = false, @@ -39,6 +40,7 @@ internal class AppSettingsUiStateMapperImplTest { isDefaultSmsApp = true, defaultSmsAppLabel = FORMATTED_DEFAULT_SMS_APP_LABEL, sendSoundEnabled = false, + youTubeLinkPreviewsEnabled = true, isDebugEnabled = true, dumpSmsEnabled = true, dumpMmsEnabled = false, diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messagedetails/MessageDetailsViewModelTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messagedetails/MessageDetailsViewModelTest.kt index 8de9a7968..e251bbdfd 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messagedetails/MessageDetailsViewModelTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messagedetails/MessageDetailsViewModelTest.kt @@ -2,6 +2,7 @@ package com.android.messaging.ui.conversation.messagedetails import android.content.ClipboardManager import androidx.lifecycle.SavedStateHandle +import com.android.messaging.data.appsettings.repository.AppSettingsRepository import com.android.messaging.data.conversation.model.message.ConversationMessageDetails import com.android.messaging.data.conversation.model.message.ConversationMessageDetailsResult import com.android.messaging.data.conversation.repository.ConversationsRepository @@ -28,6 +29,9 @@ internal class MessageDetailsViewModelTest { val mainDispatcherRule = MainDispatcherRule() private val conversationsRepository = mockk() + private val appSettingsRepository = mockk { + coEvery { isYouTubeLinkPreviewsEnabled() } returns false + } private val messageDetailsUiStateMapper = mockk() private val clipboardManager = mockk() @@ -58,6 +62,7 @@ internal class MessageDetailsViewModelTest { messageDetailsUiStateMapper.map( message = message, details = details, + isYouTubePreviewEnabled = false, ) } returns content @@ -79,6 +84,7 @@ internal class MessageDetailsViewModelTest { messageDetailsUiStateMapper.map( message = message, details = details, + isYouTubePreviewEnabled = false, ) } } @@ -96,6 +102,7 @@ internal class MessageDetailsViewModelTest { messageDetailsUiStateMapper.map( message = null, details = null, + isYouTubePreviewEnabled = false, ) } returns MessageDetailsUiState.Unavailable @@ -128,6 +135,7 @@ internal class MessageDetailsViewModelTest { ): MessageDetailsViewModel { return MessageDetailsViewModel( conversationsRepository = conversationsRepository, + appSettingsRepository = appSettingsRepository, messageDetailsUiStateMapper = messageDetailsUiStateMapper, clipboardManager = clipboardManager, savedStateHandle = savedStateHandle, diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messagedetails/mapper/MessageDetailsUiStateMapperImplTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messagedetails/mapper/MessageDetailsUiStateMapperImplTest.kt index 0c6f394cf..e0a2a2114 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messagedetails/mapper/MessageDetailsUiStateMapperImplTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messagedetails/mapper/MessageDetailsUiStateMapperImplTest.kt @@ -25,11 +25,17 @@ internal class MessageDetailsUiStateMapperImplTest { val message = mockk() val preview = mockk() - every { conversationMessageUiModelMapper.map(data = message) } returns preview + every { + conversationMessageUiModelMapper.map( + data = message, + isYouTubePreviewEnabled = false, + ) + } returns preview val result = mapper.map( message = message, details = details(), + isYouTubePreviewEnabled = false, ) val content = result as MessageDetailsUiState.Content @@ -41,6 +47,7 @@ internal class MessageDetailsUiStateMapperImplTest { val result = mapper.map( message = null, details = details(), + isYouTubePreviewEnabled = false, ) assertEquals(MessageDetailsUiState.Unavailable, result) @@ -51,6 +58,7 @@ internal class MessageDetailsUiStateMapperImplTest { val result = mapper.map( message = mockk(), details = null, + isYouTubePreviewEnabled = false, ) assertEquals(MessageDetailsUiState.Unavailable, result) diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/ConversationMessagesDelegateImplTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/ConversationMessagesDelegateImplTest.kt index 052021d2c..3934c8a4d 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/ConversationMessagesDelegateImplTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/ConversationMessagesDelegateImplTest.kt @@ -1,6 +1,7 @@ package com.android.messaging.ui.conversation.messages.delegate import androidx.core.net.toUri +import com.android.messaging.data.appsettings.repository.AppSettingsRepository import com.android.messaging.data.conversation.repository.ConversationVCardMetadataRepository import com.android.messaging.data.conversation.repository.ConversationsRepository import com.android.messaging.domain.photoviewer.usecase.ResolveConversationPhotoViewerInitialOccurrenceIndex @@ -23,6 +24,7 @@ internal class ConversationMessagesDelegateImplTest { mockk() private val delegate = ConversationMessagesDelegateImpl( conversationsRepository = mockk(), + appSettingsRepository = mockk(), resolveInitialPhotoOccurrenceIndex = resolveInitialPhotoOccurrenceIndex, conversationMessageUiModelMapper = mockk(), conversationVCardAttachmentUiModelMapper = diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/conversationmessagesdelegate/BaseConversationMessagesDelegateTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/conversationmessagesdelegate/BaseConversationMessagesDelegateTest.kt index f429cc147..7ea53a3bf 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/conversationmessagesdelegate/BaseConversationMessagesDelegateTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/conversationmessagesdelegate/BaseConversationMessagesDelegateTest.kt @@ -1,6 +1,7 @@ package com.android.messaging.ui.conversation.messages.delegate.conversationmessagesdelegate import android.net.Uri +import com.android.messaging.data.appsettings.repository.AppSettingsRepository import com.android.messaging.data.conversation.model.attachment.ConversationVCardAttachmentMetadata import com.android.messaging.data.conversation.model.attachment.ConversationVCardAttachmentType import com.android.messaging.data.conversation.repository.ConversationVCardMetadataRepository @@ -15,6 +16,7 @@ import com.android.messaging.ui.conversation.messages.delegate.ConversationMessa import com.android.messaging.ui.conversation.messages.mapper.ConversationMessageUiModelMapper import com.android.messaging.ui.conversation.messages.model.message.ConversationMessagePartUiModel import com.android.messaging.ui.conversation.messages.model.message.ConversationMessageUiModel +import io.mockk.coEvery import io.mockk.every import io.mockk.mockk import kotlinx.collections.immutable.toImmutableList @@ -31,6 +33,9 @@ internal abstract class BaseConversationMessagesDelegateTest { val mainDispatcherRule = MainDispatcherRule() protected val conversationsRepository = mockk() + protected val appSettingsRepository = mockk { + coEvery { isYouTubeLinkPreviewsEnabled() } returns false + } protected val messageUiModelMapper = mockk() protected val vCardUiModelMapper = mockk() protected val vCardMetadataRepository = mockk() @@ -38,6 +43,7 @@ internal abstract class BaseConversationMessagesDelegateTest { protected fun createDelegate(): ConversationMessagesDelegateImpl { return ConversationMessagesDelegateImpl( conversationsRepository = conversationsRepository, + appSettingsRepository = appSettingsRepository, resolveInitialPhotoOccurrenceIndex = mockk(relaxed = true), conversationMessageUiModelMapper = messageUiModelMapper, @@ -90,7 +96,12 @@ internal abstract class BaseConversationMessagesDelegateTest { ): List { return uiModels.map { uiModel -> mockk(relaxed = true).also { data -> - every { messageUiModelMapper.map(data = data) } returns uiModel + every { + messageUiModelMapper.map( + data = data, + isYouTubePreviewEnabled = any(), + ) + } returns uiModel } } } @@ -123,6 +134,7 @@ internal abstract class BaseConversationMessagesDelegateTest { canForwardMessage = false, canResendMessage = false, canSaveAttachments = false, + isYouTubePreviewEnabled = false, mmsDownload = null, mmsSubject = null, protocol = ConversationMessageUiModel.Protocol.SMS, diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/conversationmessagesdelegate/ConversationMessagesDelegateMessagesTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/conversationmessagesdelegate/ConversationMessagesDelegateMessagesTest.kt index 5e09c3491..d6bc28624 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/conversationmessagesdelegate/ConversationMessagesDelegateMessagesTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/conversationmessagesdelegate/ConversationMessagesDelegateMessagesTest.kt @@ -78,8 +78,18 @@ internal class ConversationMessagesDelegateMessagesTest : BaseConversationMessag ConversationMessagesUiState.Present(persistentListOf(first, second)), delegate.state.value, ) - verify(exactly = 1) { messageUiModelMapper.map(data = messageData[0]) } - verify(exactly = 1) { messageUiModelMapper.map(data = messageData[1]) } + verify(exactly = 1) { + messageUiModelMapper.map( + data = messageData[0], + isYouTubePreviewEnabled = false, + ) + } + verify(exactly = 1) { + messageUiModelMapper.map( + data = messageData[1], + isYouTubePreviewEnabled = false, + ) + } } } diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/conversationmessagesdelegate/ConversationMessagesDelegateYouTubePreviewTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/conversationmessagesdelegate/ConversationMessagesDelegateYouTubePreviewTest.kt new file mode 100644 index 000000000..4b467ba96 --- /dev/null +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/conversationmessagesdelegate/ConversationMessagesDelegateYouTubePreviewTest.kt @@ -0,0 +1,79 @@ +package com.android.messaging.ui.conversation.messages.delegate.conversationmessagesdelegate + +import com.android.messaging.datamodel.data.ConversationMessageData +import com.android.messaging.testutil.TEST_CONVERSATION_ID as CONVERSATION_ID +import com.android.messaging.ui.conversation.messages.model.message.ConversationMessagesUiState +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(RobolectricTestRunner::class) +internal class ConversationMessagesDelegateYouTubePreviewTest : + BaseConversationMessagesDelegateTest() { + + @Test + fun refresh_forwardsUpdatedYouTubeLinkPreviewsPreferenceToMapper() { + runTest(context = mainDispatcherRule.testDispatcher) { + val messageData = mockk() + val disabledMessage = messageUiModel(messageId = "disabled") + val enabledMessage = messageUiModel(messageId = "enabled") + coEvery { + appSettingsRepository.isYouTubeLinkPreviewsEnabled() + } returnsMany listOf(false, true) + every { + messageUiModelMapper.map( + data = messageData, + isYouTubePreviewEnabled = false, + ) + } returns disabledMessage + every { + messageUiModelMapper.map( + data = messageData, + isYouTubePreviewEnabled = true, + ) + } returns enabledMessage + givenConversationMessages(messages = flowOf(listOf(messageData))) + val delegate = createBoundDelegate( + conversationIdFlow = MutableStateFlow(CONVERSATION_ID), + ) + runCurrent() + + assertEquals( + ConversationMessagesUiState.Present(persistentListOf(disabledMessage)), + delegate.state.value, + ) + + delegate.refresh() + runCurrent() + + assertEquals( + ConversationMessagesUiState.Present(persistentListOf(enabledMessage)), + delegate.state.value, + ) + verify(exactly = 1) { + messageUiModelMapper.map( + data = messageData, + isYouTubePreviewEnabled = false, + ) + } + verify(exactly = 1) { + messageUiModelMapper.map( + data = messageData, + isYouTubePreviewEnabled = true, + ) + } + } + } +} diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/selection/BaseConversationMessageSelectionDelegateTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/selection/BaseConversationMessageSelectionDelegateTest.kt index c992b8928..66ebc1649 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/selection/BaseConversationMessageSelectionDelegateTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/selection/BaseConversationMessageSelectionDelegateTest.kt @@ -138,6 +138,7 @@ internal abstract class BaseConversationMessageSelectionDelegateTest { canForwardMessage = canForwardMessage, canResendMessage = canResendMessage, canSaveAttachments = canSaveAttachments, + isYouTubePreviewEnabled = false, mmsDownload = null, mmsSubject = null, protocol = ConversationMessageUiModel.Protocol.SMS, diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperMappingTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperMappingTest.kt index b2436539b..f55884a80 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperMappingTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperMappingTest.kt @@ -47,6 +47,7 @@ internal class ConversationMessageUiModelMapperMappingTest : mmsSubject = "Subject line", isSms = true, ), + isYouTubePreviewEnabled = true, ) assertEquals( @@ -74,6 +75,7 @@ internal class ConversationMessageUiModelMapperMappingTest : canForwardMessage = true, canResendMessage = false, canSaveAttachments = false, + isYouTubePreviewEnabled = true, mmsDownload = null, mmsSubject = "Subject line", protocol = Protocol.SMS, @@ -86,6 +88,7 @@ internal class ConversationMessageUiModelMapperMappingTest : fun map_withNullMessageIdAndConversationId_substitutesEmptyStrings() { val uiModel = mapper.map( messageData(messageId = null, conversationId = null), + isYouTubePreviewEnabled = false, ) assertEquals("", uiModel.messageId) @@ -94,7 +97,7 @@ internal class ConversationMessageUiModelMapperMappingTest : @Test fun map_withNullParts_returnsEmptyPartsList() { - val uiModel = mapper.map(messageData(parts = null)) + val uiModel = mapper.map(messageData(parts = null), isYouTubePreviewEnabled = false) assertEquals(persistentListOf(), uiModel.parts) } @@ -107,6 +110,7 @@ internal class ConversationMessageUiModelMapperMappingTest : senderParticipantId = "", selfParticipantId = " ", ), + isYouTubePreviewEnabled = false, ) assertNull(uiModel.senderNormalizedDestination) diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperMmsDownloadTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperMmsDownloadTest.kt index 04ce8ad3f..c77d62bc8 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperMmsDownloadTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperMmsDownloadTest.kt @@ -20,6 +20,7 @@ internal class ConversationMessageUiModelMapperMmsDownloadTest : smsMessageSize = 2_048, mmsExpiry = 1_700_000_000_000L, ), + isYouTubePreviewEnabled = false, ) assertEquals( @@ -42,7 +43,7 @@ internal class ConversationMessageUiModelMapperMmsDownloadTest : ) downloadingStatuses.forEach { status -> - val uiModel = mapper.map(messageData(status = status)) + val uiModel = mapper.map(messageData(status = status), isYouTubePreviewEnabled = false) assertEquals( "status=$status", @@ -56,6 +57,7 @@ internal class ConversationMessageUiModelMapperMmsDownloadTest : fun map_downloadFailedStatus_buildsDownloadFailedMmsDownload() { val uiModel = mapper.map( messageData(status = MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED), + isYouTubePreviewEnabled = false, ) assertEquals( @@ -68,6 +70,7 @@ internal class ConversationMessageUiModelMapperMmsDownloadTest : fun map_expiredStatus_buildsExpiredOrUnavailableMmsDownload() { val uiModel = mapper.map( messageData(status = MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE), + isYouTubePreviewEnabled = false, ) assertEquals( @@ -85,7 +88,7 @@ internal class ConversationMessageUiModelMapperMmsDownloadTest : ) nonDownloadStatuses.forEach { status -> - val uiModel = mapper.map(messageData(status = status)) + val uiModel = mapper.map(messageData(status = status), isYouTubePreviewEnabled = false) assertNull("status=$status", uiModel.mmsDownload) } diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperPartsTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperPartsTest.kt index d48148e4f..888465680 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperPartsTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperPartsTest.kt @@ -21,6 +21,7 @@ internal class ConversationMessageUiModelMapperPartsTest : messageData( parts = listOf(messagePart(contentType = "text/plain", text = "Hi Ada")), ), + isYouTubePreviewEnabled = false, ) assertEquals( @@ -35,6 +36,7 @@ internal class ConversationMessageUiModelMapperPartsTest : messageData( parts = listOf(messagePart(contentType = "text/html", text = null)), ), + isYouTubePreviewEnabled = false, ) assertEquals( @@ -59,6 +61,7 @@ internal class ConversationMessageUiModelMapperPartsTest : ), ), ), + isYouTubePreviewEnabled = false, ) assertEquals( @@ -91,6 +94,7 @@ internal class ConversationMessageUiModelMapperPartsTest : ), ), ), + isYouTubePreviewEnabled = false, ) assertEquals( @@ -122,6 +126,7 @@ internal class ConversationMessageUiModelMapperPartsTest : ), ), ), + isYouTubePreviewEnabled = false, ) assertEquals( @@ -148,6 +153,7 @@ internal class ConversationMessageUiModelMapperPartsTest : messagePart(contentType = "text/x-vCard", contentUri = contentUri), ), ), + isYouTubePreviewEnabled = false, ) assertEquals( @@ -178,6 +184,7 @@ internal class ConversationMessageUiModelMapperPartsTest : messagePart(contentType = "application/pdf", contentUri = contentUri), ), ), + isYouTubePreviewEnabled = false, ) assertEquals( @@ -198,6 +205,7 @@ internal class ConversationMessageUiModelMapperPartsTest : fun map_nullContentType_mapsToFileAttachmentWithEmptyContentType() { val uiModel = mapper.map( messageData(parts = listOf(messagePart(contentType = null))), + isYouTubePreviewEnabled = false, ) assertEquals( @@ -225,6 +233,7 @@ internal class ConversationMessageUiModelMapperPartsTest : messagePart(contentType = "image/png", contentUri = imageUri), ), ), + isYouTubePreviewEnabled = false, ) assertEquals( @@ -253,6 +262,7 @@ internal class ConversationMessageUiModelMapperPartsTest : ), ), ), + isYouTubePreviewEnabled = false, ) assertTrue(uiModel.canSaveAttachments) @@ -270,6 +280,7 @@ internal class ConversationMessageUiModelMapperPartsTest : ), ), ), + isYouTubePreviewEnabled = false, ) assertFalse(uiModel.canSaveAttachments) @@ -281,6 +292,7 @@ internal class ConversationMessageUiModelMapperPartsTest : messageData( parts = listOf(messagePart(contentType = "image/jpeg", contentUri = null)), ), + isYouTubePreviewEnabled = false, ) assertFalse(uiModel.canSaveAttachments) @@ -297,6 +309,7 @@ internal class ConversationMessageUiModelMapperPartsTest : ), ), ), + isYouTubePreviewEnabled = false, ) assertFalse(uiModel.canSaveAttachments) @@ -314,6 +327,7 @@ internal class ConversationMessageUiModelMapperPartsTest : ), ), ), + isYouTubePreviewEnabled = false, ) assertTrue(uiModel.canSaveAttachments) @@ -331,6 +345,7 @@ internal class ConversationMessageUiModelMapperPartsTest : messagePart(contentType = "text/plain", text = "caption"), ), ), + isYouTubePreviewEnabled = false, ) assertTrue(uiModel.canSaveAttachments) @@ -347,6 +362,7 @@ internal class ConversationMessageUiModelMapperPartsTest : ), ), ), + isYouTubePreviewEnabled = false, ) assertTrue(uiModel.canSaveAttachments) @@ -362,6 +378,7 @@ internal class ConversationMessageUiModelMapperPartsTest : messagePart(contentType = "application/ogg", contentUri = contentUri), ), ), + isYouTubePreviewEnabled = false, ) assertEquals( @@ -388,6 +405,7 @@ internal class ConversationMessageUiModelMapperPartsTest : messagePart(contentType = "text/x-vcard", contentUri = contentUri), ), ), + isYouTubePreviewEnabled = false, ) assertEquals( diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperProtocolAndTimestampTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperProtocolAndTimestampTest.kt index 90224f35f..d20bf5a56 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperProtocolAndTimestampTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperProtocolAndTimestampTest.kt @@ -14,6 +14,7 @@ internal class ConversationMessageUiModelMapperProtocolAndTimestampTest : fun map_smsMessage_mapsToSmsProtocol() { val uiModel = mapper.map( messageData(isSms = true, isMms = false, isMmsNotification = false), + isYouTubePreviewEnabled = false, ) assertEquals(Protocol.SMS, uiModel.protocol) @@ -23,6 +24,7 @@ internal class ConversationMessageUiModelMapperProtocolAndTimestampTest : fun map_mmsNotification_mapsToMmsPushNotificationProtocolBeforeMms() { val uiModel = mapper.map( messageData(isSms = false, isMmsNotification = true, isMms = true), + isYouTubePreviewEnabled = false, ) assertEquals(Protocol.MMS_PUSH_NOTIFICATION, uiModel.protocol) @@ -32,6 +34,7 @@ internal class ConversationMessageUiModelMapperProtocolAndTimestampTest : fun map_mmsMessage_mapsToMmsProtocol() { val uiModel = mapper.map( messageData(isSms = false, isMmsNotification = false, isMms = true), + isYouTubePreviewEnabled = false, ) assertEquals(Protocol.MMS, uiModel.protocol) @@ -41,6 +44,7 @@ internal class ConversationMessageUiModelMapperProtocolAndTimestampTest : fun map_nonTelephonyMessage_mapsToUnknownProtocol() { val uiModel = mapper.map( messageData(isSms = false, isMmsNotification = false, isMms = false), + isYouTubePreviewEnabled = false, ) assertEquals(Protocol.UNKNOWN, uiModel.protocol) @@ -50,6 +54,7 @@ internal class ConversationMessageUiModelMapperProtocolAndTimestampTest : fun map_incomingMessage_usesReceivedTimestampAsDisplayTimestamp() { val uiModel = mapper.map( messageData(isIncoming = true, receivedTimestamp = 500L, sentTimestamp = 100L), + isYouTubePreviewEnabled = false, ) assertEquals(500L, uiModel.displayTimestamp) @@ -59,6 +64,7 @@ internal class ConversationMessageUiModelMapperProtocolAndTimestampTest : fun map_incomingMessageWithoutReceivedTimestamp_fallsBackToSentTimestamp() { val uiModel = mapper.map( messageData(isIncoming = true, receivedTimestamp = 0L, sentTimestamp = 100L), + isYouTubePreviewEnabled = false, ) assertEquals(100L, uiModel.displayTimestamp) @@ -68,6 +74,7 @@ internal class ConversationMessageUiModelMapperProtocolAndTimestampTest : fun map_outgoingMessage_usesSentTimestampAsDisplayTimestamp() { val uiModel = mapper.map( messageData(isIncoming = false, sentTimestamp = 300L, receivedTimestamp = 900L), + isYouTubePreviewEnabled = false, ) assertEquals(300L, uiModel.displayTimestamp) @@ -77,6 +84,7 @@ internal class ConversationMessageUiModelMapperProtocolAndTimestampTest : fun map_outgoingMessageWithoutSentTimestamp_fallsBackToReceivedTimestamp() { val uiModel = mapper.map( messageData(isIncoming = false, sentTimestamp = 0L, receivedTimestamp = 900L), + isYouTubePreviewEnabled = false, ) assertEquals(900L, uiModel.displayTimestamp) @@ -86,6 +94,7 @@ internal class ConversationMessageUiModelMapperProtocolAndTimestampTest : fun map_outgoingMessageWithNegativeSentTimestamp_fallsBackToReceivedTimestamp() { val uiModel = mapper.map( messageData(isIncoming = false, sentTimestamp = -1L, receivedTimestamp = 900L), + isYouTubePreviewEnabled = false, ) assertEquals(900L, uiModel.displayTimestamp) diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperStatusTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperStatusTest.kt index d2a18e5ff..3334249de 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperStatusTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperStatusTest.kt @@ -41,7 +41,10 @@ internal class ConversationMessageUiModelMapperStatusTest : ) statusToUiStatus.forEach { (bugleStatus, expectedUiStatus) -> - val uiModel = mapper.map(messageData(status = bugleStatus)) + val uiModel = mapper.map( + messageData(status = bugleStatus), + isYouTubePreviewEnabled = false, + ) assertEquals("status=$bugleStatus", expectedUiStatus, uiModel.status) } @@ -49,7 +52,7 @@ internal class ConversationMessageUiModelMapperStatusTest : @Test fun map_withUnexpectedStatus_mapsToUnknown() { - val uiModel = mapper.map(messageData(status = 9999)) + val uiModel = mapper.map(messageData(status = 9999), isYouTubePreviewEnabled = false) assertEquals(Status.Unknown, uiModel.status) } diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/list/ConversationMessagesListRenderingTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/list/ConversationMessagesListRenderingTest.kt index f07925cf7..d130448d5 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/list/ConversationMessagesListRenderingTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/list/ConversationMessagesListRenderingTest.kt @@ -280,6 +280,7 @@ internal class ConversationMessagesListRenderingTest { canForwardMessage = true, canResendMessage = false, canSaveAttachments = false, + isYouTubePreviewEnabled = false, mmsDownload = null, mmsSubject = null, protocol = ConversationMessageUiModel.Protocol.SMS, diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageContentBuilderTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageContentBuilderTest.kt index 8d90d424e..ca67293b6 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageContentBuilderTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageContentBuilderTest.kt @@ -164,6 +164,7 @@ class ConversationMessageContentBuilderTest { canForwardMessage = true, canResendMessage = false, canSaveAttachments = false, + isYouTubePreviewEnabled = false, mmsDownload = null, mmsSubject = null, protocol = ConversationMessageUiModel.Protocol.MMS, diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageContentBuilderYouTubePreviewTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageContentBuilderYouTubePreviewTest.kt new file mode 100644 index 000000000..4b4deace8 --- /dev/null +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageContentBuilderYouTubePreviewTest.kt @@ -0,0 +1,113 @@ +package com.android.messaging.ui.conversation.messages.ui.message + +import androidx.core.net.toUri +import com.android.messaging.ui.conversation.messages.model.attachment.ConversationMessageAttachment +import com.android.messaging.ui.conversation.messages.model.message.ConversationMessagePartUiModel +import com.android.messaging.ui.conversation.messages.model.message.ConversationMessageUiModel +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +internal class ConversationMessageContentBuilderYouTubePreviewTest { + + @Test + fun buildContentSkipsYouTubePreviewWhenDisabled() { + val content = buildConversationMessageContent( + message = message( + text = MESSAGE_TEXT_WITH_YOUTUBE_LINK, + isYouTubePreviewEnabled = false, + ), + subjectText = null, + ) + + assertTrue(content.attachments.isEmpty()) + } + + @Test + fun buildContentAppendsYouTubePreviewWhenEnabled() { + val content = buildConversationMessageContent( + message = message( + text = MESSAGE_TEXT_WITH_YOUTUBE_LINK, + isYouTubePreviewEnabled = true, + ), + subjectText = null, + ) + + val preview = content.attachments.single() + as ConversationMessageAttachment.YouTubePreview + assertEquals(YOUTUBE_VIDEO_URL, preview.sourceUrl) + } + + @Test + fun buildContentSkipsYouTubePreviewWhenMessageHasImageAttachment() { + val content = buildConversationMessageContent( + message = message( + text = MESSAGE_TEXT_WITH_YOUTUBE_LINK, + isYouTubePreviewEnabled = true, + parts = persistentListOf( + ConversationMessagePartUiModel.Attachment.Image( + text = null, + contentType = "image/jpeg", + contentUri = "content://example.test/image/1".toUri(), + width = 320, + height = 240, + partId = "part-1", + ), + ), + ), + subjectText = null, + ) + + assertTrue( + content.attachments.none { attachment -> + attachment is ConversationMessageAttachment.YouTubePreview + }, + ) + } + + private fun message( + text: String, + isYouTubePreviewEnabled: Boolean, + parts: ImmutableList = persistentListOf(), + ): ConversationMessageUiModel { + return ConversationMessageUiModel( + messageId = "message-1", + conversationId = "conversation-1", + text = text, + parts = parts, + sentTimestamp = 0L, + receivedTimestamp = 0L, + displayTimestamp = 0L, + status = ConversationMessageUiModel.Status.Incoming.Complete, + isIncoming = true, + senderDisplayName = null, + senderAvatarUri = null, + senderContactId = 0L, + senderContactLookupKey = null, + senderNormalizedDestination = null, + senderParticipantId = null, + selfParticipantId = null, + canClusterWithPrevious = false, + canClusterWithNext = false, + canCopyMessageToClipboard = false, + canDownloadMessage = false, + canForwardMessage = false, + canResendMessage = false, + canSaveAttachments = false, + isYouTubePreviewEnabled = isYouTubePreviewEnabled, + mmsDownload = null, + mmsSubject = null, + protocol = ConversationMessageUiModel.Protocol.SMS, + ) + } + + private companion object { + private const val YOUTUBE_VIDEO_URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + private const val MESSAGE_TEXT_WITH_YOUTUBE_LINK = "Watch this: $YOUTUBE_VIDEO_URL" + } +} diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageDateFormattingTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageDateFormattingTest.kt index 65468f2a9..0c8594468 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageDateFormattingTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageDateFormattingTest.kt @@ -153,6 +153,7 @@ internal class ConversationMessageDateFormattingTest { canForwardMessage = true, canResendMessage = false, canSaveAttachments = false, + isYouTubePreviewEnabled = false, mmsDownload = null, mmsSubject = null, protocol = ConversationMessageUiModel.Protocol.SMS, diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ResolveConversationMessageSimDisplayNameTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ResolveConversationMessageSimDisplayNameTest.kt index 6904f3df2..63f30f47b 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ResolveConversationMessageSimDisplayNameTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ResolveConversationMessageSimDisplayNameTest.kt @@ -141,6 +141,7 @@ class ResolveConversationMessageSimDisplayNameTest { canForwardMessage = false, canResendMessage = false, canSaveAttachments = false, + isYouTubePreviewEnabled = false, mmsDownload = mmsDownload, mmsSubject = null, protocol = ConversationMessageUiModel.Protocol.SMS, diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/screen/viewmodel/BaseConversationViewModelTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/screen/viewmodel/BaseConversationViewModelTest.kt index 448b0f75c..0af9051cc 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/screen/viewmodel/BaseConversationViewModelTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/screen/viewmodel/BaseConversationViewModelTest.kt @@ -383,6 +383,7 @@ internal abstract class BaseConversationViewModelTest { canForwardMessage = false, canResendMessage = false, canSaveAttachments = false, + isYouTubePreviewEnabled = false, mmsDownload = null, mmsSubject = null, protocol = ConversationMessageUiModel.Protocol.SMS, diff --git a/res/values/constants.xml b/res/values/constants.xml index 5d3faba8c..0d97f0495 100644 --- a/res/values/constants.xml +++ b/res/values/constants.xml @@ -30,6 +30,8 @@ sms_enabled send_sound true + youtube_link_previews + false advanced_prefs General settings + + Privacy + + + YouTube link previews + + + Show video thumbnails for YouTube links in conversations. Enabling this fetches thumbnails from YouTube and may reduce privacy + Advanced settings diff --git a/src/com/android/messaging/data/appsettings/model/AppBooleanPref.kt b/src/com/android/messaging/data/appsettings/model/AppBooleanPref.kt index 1155b113e..4b11c76f5 100644 --- a/src/com/android/messaging/data/appsettings/model/AppBooleanPref.kt +++ b/src/com/android/messaging/data/appsettings/model/AppBooleanPref.kt @@ -7,6 +7,7 @@ internal enum class AppBooleanPref( @param:StringRes val keyResId: Int, ) { SEND_SOUND(R.string.send_sound_pref_key), + YOUTUBE_LINK_PREVIEWS(R.string.youtube_link_previews_pref_key), DUMP_SMS(R.string.dump_sms_pref_key), DUMP_MMS(R.string.dump_mms_pref_key), } diff --git a/src/com/android/messaging/data/appsettings/model/AppSettings.kt b/src/com/android/messaging/data/appsettings/model/AppSettings.kt index 057b436b7..863563430 100644 --- a/src/com/android/messaging/data/appsettings/model/AppSettings.kt +++ b/src/com/android/messaging/data/appsettings/model/AppSettings.kt @@ -4,6 +4,7 @@ internal data class AppSettings( val isDefaultSmsApp: Boolean, val defaultSmsAppLabel: String, val sendSoundEnabled: Boolean, + val youTubeLinkPreviewsEnabled: Boolean, val isDebugEnabled: Boolean, val dumpSmsEnabled: Boolean, val dumpMmsEnabled: Boolean, diff --git a/src/com/android/messaging/data/appsettings/repository/AppSettingsRepository.kt b/src/com/android/messaging/data/appsettings/repository/AppSettingsRepository.kt index 8f3a736e4..4096c4a95 100644 --- a/src/com/android/messaging/data/appsettings/repository/AppSettingsRepository.kt +++ b/src/com/android/messaging/data/appsettings/repository/AppSettingsRepository.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.withContext internal interface AppSettingsRepository { suspend fun getAppSettings(): AppSettings + suspend fun isYouTubeLinkPreviewsEnabled(): Boolean suspend fun setBooleanPref(pref: AppBooleanPref, enabled: Boolean) } @@ -37,6 +38,7 @@ internal class AppSettingsRepositoryImpl @Inject constructor( context.getString(R.string.send_sound_pref_key), resources.getBoolean(R.bool.send_sound_pref_default), ), + youTubeLinkPreviewsEnabled = readYouTubeLinkPreviewsEnabled(), isDebugEnabled = debugFeaturesProvider.isEnabled(), dumpSmsEnabled = appPrefs.getBoolean( context.getString(R.string.dump_sms_pref_key), @@ -50,6 +52,12 @@ internal class AppSettingsRepositoryImpl @Inject constructor( } } + override suspend fun isYouTubeLinkPreviewsEnabled(): Boolean { + return withContext(ioDispatcher) { + readYouTubeLinkPreviewsEnabled() + } + } + override suspend fun setBooleanPref( pref: AppBooleanPref, enabled: Boolean, @@ -61,4 +69,11 @@ internal class AppSettingsRepositoryImpl @Inject constructor( ) } } + + private fun readYouTubeLinkPreviewsEnabled(): Boolean { + return BuglePrefs.getApplicationPrefs().getBoolean( + context.getString(R.string.youtube_link_previews_pref_key), + context.resources.getBoolean(R.bool.youtube_link_previews_pref_default), + ) + } } diff --git a/src/com/android/messaging/ui/appsettings/general/delegate/AppSettingsDelegate.kt b/src/com/android/messaging/ui/appsettings/general/delegate/AppSettingsDelegate.kt index a65eebcc8..67ab4e1b4 100644 --- a/src/com/android/messaging/ui/appsettings/general/delegate/AppSettingsDelegate.kt +++ b/src/com/android/messaging/ui/appsettings/general/delegate/AppSettingsDelegate.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.launch internal interface AppSettingsDelegate : SettingsScreenDelegate { fun onSendSoundChanged(enabled: Boolean) + fun onYouTubeLinkPreviewsChanged(enabled: Boolean) fun onDumpSmsChanged(enabled: Boolean) fun onDumpMmsChanged(enabled: Boolean) } @@ -63,6 +64,13 @@ internal class AppSettingsDelegateImpl @Inject constructor( ) } + override fun onYouTubeLinkPreviewsChanged(enabled: Boolean) { + setBooleanPref( + pref = AppBooleanPref.YOUTUBE_LINK_PREVIEWS, + enabled = enabled, + ) + } + override fun onDumpSmsChanged(enabled: Boolean) { setBooleanPref( pref = AppBooleanPref.DUMP_SMS, diff --git a/src/com/android/messaging/ui/appsettings/general/mapper/AppSettingsUiStateMapper.kt b/src/com/android/messaging/ui/appsettings/general/mapper/AppSettingsUiStateMapper.kt index a80504970..d314b7a59 100644 --- a/src/com/android/messaging/ui/appsettings/general/mapper/AppSettingsUiStateMapper.kt +++ b/src/com/android/messaging/ui/appsettings/general/mapper/AppSettingsUiStateMapper.kt @@ -23,6 +23,7 @@ internal class AppSettingsUiStateMapperImpl @Inject constructor( appSettings.defaultSmsAppLabel, ), sendSoundEnabled = appSettings.sendSoundEnabled, + youTubeLinkPreviewsEnabled = appSettings.youTubeLinkPreviewsEnabled, isDebugEnabled = appSettings.isDebugEnabled, dumpSmsEnabled = appSettings.dumpSmsEnabled, dumpMmsEnabled = appSettings.dumpMmsEnabled, diff --git a/src/com/android/messaging/ui/appsettings/general/model/AppSettingsUiState.kt b/src/com/android/messaging/ui/appsettings/general/model/AppSettingsUiState.kt index 1d11181e7..cce5f03ee 100644 --- a/src/com/android/messaging/ui/appsettings/general/model/AppSettingsUiState.kt +++ b/src/com/android/messaging/ui/appsettings/general/model/AppSettingsUiState.kt @@ -7,6 +7,7 @@ internal data class AppSettingsUiState( val isDefaultSmsApp: Boolean = false, val defaultSmsAppLabel: String = "", val sendSoundEnabled: Boolean = true, + val youTubeLinkPreviewsEnabled: Boolean = false, val isDebugEnabled: Boolean = false, val dumpSmsEnabled: Boolean = false, val dumpMmsEnabled: Boolean = false, diff --git a/src/com/android/messaging/ui/appsettings/general/ui/AppSettingsScreen.kt b/src/com/android/messaging/ui/appsettings/general/ui/AppSettingsScreen.kt index 06cdcbfd6..add12fe3f 100644 --- a/src/com/android/messaging/ui/appsettings/general/ui/AppSettingsScreen.kt +++ b/src/com/android/messaging/ui/appsettings/general/ui/AppSettingsScreen.kt @@ -28,6 +28,7 @@ internal fun AppSettingsScreen( appSettings: AppSettingsUiState, onAction: (Action) -> Unit, onNavigateBack: () -> Unit, + onPrivacyClick: () -> Unit, modifier: Modifier = Modifier, isTopLevel: Boolean = false, onAdvancedClick: (() -> Unit)? = null, @@ -56,6 +57,7 @@ internal fun AppSettingsScreen( coreSettingsItems( appSettings = appSettings, onAction = onAction, + onPrivacyClick = onPrivacyClick, ) if (isTopLevel && onAdvancedClick != null) { @@ -75,6 +77,7 @@ internal fun AppSettingsScreen( private fun LazyListScope.coreSettingsItems( appSettings: AppSettingsUiState, onAction: (Action) -> Unit, + onPrivacyClick: () -> Unit, ) { item(key = "default_sms_app") { SettingsClickableItem( @@ -104,6 +107,13 @@ private fun LazyListScope.coreSettingsItems( }, ) } + + item(key = "privacy") { + SettingsClickableItem( + title = stringResource(R.string.privacy_settings_activity_title), + onClick = onPrivacyClick, + ) + } } private fun LazyListScope.advancedSettingsItem(onAdvancedClick: () -> Unit) { @@ -179,6 +189,7 @@ private fun AppSettingsScreenTopLevelPreview() { ), onAction = {}, onNavigateBack = {}, + onPrivacyClick = {}, isTopLevel = true, onAdvancedClick = {}, ) @@ -200,6 +211,7 @@ private fun AppSettingsScreenDebugPreview() { ), onAction = {}, onNavigateBack = {}, + onPrivacyClick = {}, ) } } diff --git a/src/com/android/messaging/ui/appsettings/privacy/ui/PrivacySettingsScreen.kt b/src/com/android/messaging/ui/appsettings/privacy/ui/PrivacySettingsScreen.kt new file mode 100644 index 000000000..ae85e0979 --- /dev/null +++ b/src/com/android/messaging/ui/appsettings/privacy/ui/PrivacySettingsScreen.kt @@ -0,0 +1,70 @@ +package com.android.messaging.ui.appsettings.privacy.ui + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewLightDark +import com.android.messaging.R +import com.android.messaging.ui.appsettings.common.SettingsSwitchItem +import com.android.messaging.ui.appsettings.common.SettingsTopAppBar +import com.android.messaging.ui.appsettings.general.model.AppSettingsUiState +import com.android.messaging.ui.appsettings.screen.model.SettingsAction as Action +import com.android.messaging.ui.core.MessagingPreviewTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun PrivacySettingsScreen( + appSettings: AppSettingsUiState, + onAction: (Action) -> Unit, + onNavigateBack: () -> Unit, + modifier: Modifier = Modifier, +) { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + + Scaffold( + modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + SettingsTopAppBar( + title = stringResource(R.string.privacy_settings_activity_title), + onNavigateBack = onNavigateBack, + scrollBehavior = scrollBehavior, + ) + }, + ) { contentPadding -> + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = contentPadding, + ) { + item(key = "youtube_link_previews") { + SettingsSwitchItem( + title = stringResource(R.string.youtube_link_previews_pref_title), + summary = stringResource(R.string.youtube_link_previews_pref_summary), + checked = appSettings.youTubeLinkPreviewsEnabled, + onCheckedChange = { + onAction(Action.YouTubeLinkPreviewsChanged(it)) + }, + ) + } + } + } +} + +@PreviewLightDark +@Composable +private fun PrivacySettingsScreenPreview() { + MessagingPreviewTheme { + PrivacySettingsScreen( + appSettings = AppSettingsUiState( + youTubeLinkPreviewsEnabled = false, + ), + onAction = {}, + onNavigateBack = {}, + ) + } +} diff --git a/src/com/android/messaging/ui/appsettings/screen/SettingsNavRouteSavedState.kt b/src/com/android/messaging/ui/appsettings/screen/SettingsNavRouteSavedState.kt index 413a2da9d..08ebad436 100644 --- a/src/com/android/messaging/ui/appsettings/screen/SettingsNavRouteSavedState.kt +++ b/src/com/android/messaging/ui/appsettings/screen/SettingsNavRouteSavedState.kt @@ -13,6 +13,9 @@ internal sealed interface SettingsNavRouteSavedState : Parcelable { @Parcelize data object AppSettings : SettingsNavRouteSavedState + @Parcelize + data object Privacy : SettingsNavRouteSavedState + @Parcelize data class SubscriptionSettings( val subId: Int, @@ -37,6 +40,10 @@ private fun SettingsNavRoute.toSavedState(): SettingsNavRouteSavedState { SettingsNavRouteSavedState.AppSettings } + SettingsNavRoute.Privacy -> { + SettingsNavRouteSavedState.Privacy + } + is SettingsNavRoute.SubscriptionSettings -> { SettingsNavRouteSavedState.SubscriptionSettings( subId = subId, @@ -56,6 +63,10 @@ private fun SettingsNavRouteSavedState.toRoute(): SettingsNavRoute { SettingsNavRoute.AppSettings } + SettingsNavRouteSavedState.Privacy -> { + SettingsNavRoute.Privacy + } + is SettingsNavRouteSavedState.SubscriptionSettings -> { SettingsNavRoute.SubscriptionSettings( subId = subId, diff --git a/src/com/android/messaging/ui/appsettings/screen/SettingsScreen.kt b/src/com/android/messaging/ui/appsettings/screen/SettingsScreen.kt index ff429ef74..c62ab8c02 100644 --- a/src/com/android/messaging/ui/appsettings/screen/SettingsScreen.kt +++ b/src/com/android/messaging/ui/appsettings/screen/SettingsScreen.kt @@ -25,6 +25,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.android.messaging.datamodel.data.ParticipantData import com.android.messaging.ui.appsettings.general.ui.AppSettingsScreen +import com.android.messaging.ui.appsettings.privacy.ui.PrivacySettingsScreen import com.android.messaging.ui.appsettings.screen.model.SettingsAction as Action import com.android.messaging.ui.appsettings.screen.model.SettingsNavRoute import com.android.messaging.ui.appsettings.screen.model.SettingsUiState @@ -147,16 +148,18 @@ private fun SettingsNavHost( } is SettingsNavRoute.AppSettings -> { - val isSingleSim = uiState.isMultiSim == false - AppSettingsScreen( + AppSettingsRoute( + uiState = uiState, + onAction = onAction, + navigateUp = navigateUp, + onRouteChange = onRouteChange, + ) + } + + is SettingsNavRoute.Privacy -> { + PrivacySettingsScreen( appSettings = uiState.appSettings, onAction = onAction, - isTopLevel = isSingleSim, - onAdvancedClick = advancedClickHandler( - uiState = uiState, - isSingleSim = isSingleSim, - onRouteChange = onRouteChange, - ), onNavigateBack = navigateUp, ) } @@ -175,6 +178,31 @@ private fun SettingsNavHost( } } +@Composable +private fun AppSettingsRoute( + uiState: SettingsUiState, + onAction: (Action) -> Unit, + navigateUp: () -> Unit, + onRouteChange: (SettingsNavRoute) -> Unit, +) { + val isSingleSim = uiState.isMultiSim == false + + AppSettingsScreen( + appSettings = uiState.appSettings, + onAction = onAction, + isTopLevel = isSingleSim, + onPrivacyClick = { + onRouteChange(SettingsNavRoute.Privacy) + }, + onAdvancedClick = advancedClickHandler( + uiState = uiState, + isSingleSim = isSingleSim, + onRouteChange = onRouteChange, + ), + onNavigateBack = navigateUp, + ) +} + @Composable private fun LeaveOpenedSubscriptionIfRemoved( effectiveRoute: SettingsNavRoute, @@ -224,6 +252,10 @@ private fun buildNavigateUp( onRouteChange(SettingsNavRoute.Main) } + effectiveRoute is SettingsNavRoute.Privacy -> { + onRouteChange(SettingsNavRoute.AppSettings) + } + effectiveRoute is SettingsNavRoute.SubscriptionSettings -> { onRouteChange( if (isMultiSim == true) { diff --git a/src/com/android/messaging/ui/appsettings/screen/SettingsViewModel.kt b/src/com/android/messaging/ui/appsettings/screen/SettingsViewModel.kt index e51077ca0..4c8a24a88 100644 --- a/src/com/android/messaging/ui/appsettings/screen/SettingsViewModel.kt +++ b/src/com/android/messaging/ui/appsettings/screen/SettingsViewModel.kt @@ -70,6 +70,56 @@ internal class SettingsViewModel @Inject constructor( } override fun onAction(action: Action) { + when (action) { + is Action.AutoRetrieveMmsChanged, + is Action.AutoRetrieveMmsWhenRoamingChanged, + is Action.DeliveryReportsChanged, + is Action.GroupMmsChanged, + is Action.PhoneNumberChanged, + -> { + onSubscriptionAction(action) + } + + is Action.WirelessAlertsClicked -> { + emitEffect(Effect.OpenWirelessAlerts(action.subId)) + } + + is Action.DumpMmsChanged -> { + appSettingsDelegate.onDumpMmsChanged(action.enabled) + } + + is Action.DumpSmsChanged -> { + appSettingsDelegate.onDumpSmsChanged(action.enabled) + } + + is Action.SendSoundChanged -> { + appSettingsDelegate.onSendSoundChanged(action.enabled) + } + + is Action.YouTubeLinkPreviewsChanged -> { + appSettingsDelegate.onYouTubeLinkPreviewsChanged(action.enabled) + } + + is Action.DefaultSmsAppClicked -> { + val effect = if (action.isCurrentlyDefault) { + Effect.OpenManageDefaultApps + } else { + Effect.RequestDefaultSmsApp + } + emitEffect(effect) + } + + is Action.NotificationsClicked -> { + emitEffect(Effect.OpenNotificationSettings) + } + + is Action.LicensesClicked -> { + emitEffect(Effect.OpenLicenses) + } + } + } + + private fun onSubscriptionAction(action: Action) { when (action) { is Action.AutoRetrieveMmsChanged -> { subscriptionSettingsDelegate.onAutoRetrieveMmsChanged( @@ -106,38 +156,7 @@ internal class SettingsViewModel @Inject constructor( ) } - is Action.WirelessAlertsClicked -> { - emitEffect(Effect.OpenWirelessAlerts(action.subId)) - } - - is Action.DumpMmsChanged -> { - appSettingsDelegate.onDumpMmsChanged(action.enabled) - } - - is Action.DumpSmsChanged -> { - appSettingsDelegate.onDumpSmsChanged(action.enabled) - } - - is Action.SendSoundChanged -> { - appSettingsDelegate.onSendSoundChanged(action.enabled) - } - - is Action.DefaultSmsAppClicked -> { - val effect = if (action.isCurrentlyDefault) { - Effect.OpenManageDefaultApps - } else { - Effect.RequestDefaultSmsApp - } - emitEffect(effect) - } - - is Action.NotificationsClicked -> { - emitEffect(Effect.OpenNotificationSettings) - } - - is Action.LicensesClicked -> { - emitEffect(Effect.OpenLicenses) - } + else -> Unit } } diff --git a/src/com/android/messaging/ui/appsettings/screen/model/SettingsAction.kt b/src/com/android/messaging/ui/appsettings/screen/model/SettingsAction.kt index d4df73df7..ff22cb5d2 100644 --- a/src/com/android/messaging/ui/appsettings/screen/model/SettingsAction.kt +++ b/src/com/android/messaging/ui/appsettings/screen/model/SettingsAction.kt @@ -43,6 +43,10 @@ internal sealed interface SettingsAction { val enabled: Boolean, ) : SettingsAction + data class YouTubeLinkPreviewsChanged( + val enabled: Boolean, + ) : SettingsAction + data class DefaultSmsAppClicked( val isCurrentlyDefault: Boolean, ) : SettingsAction diff --git a/src/com/android/messaging/ui/appsettings/screen/model/SettingsNavRoute.kt b/src/com/android/messaging/ui/appsettings/screen/model/SettingsNavRoute.kt index 7230d9f6f..49fc102f1 100644 --- a/src/com/android/messaging/ui/appsettings/screen/model/SettingsNavRoute.kt +++ b/src/com/android/messaging/ui/appsettings/screen/model/SettingsNavRoute.kt @@ -15,6 +15,10 @@ internal sealed interface SettingsNavRoute { override val depth: Int = 1 } + data object Privacy : SettingsNavRoute { + override val depth: Int = 2 + } + data class SubscriptionSettings( val subId: Int, val title: String, diff --git a/src/com/android/messaging/ui/conversation/messagedetails/MessageDetailsViewModel.kt b/src/com/android/messaging/ui/conversation/messagedetails/MessageDetailsViewModel.kt index 083510340..e1a8aa03a 100644 --- a/src/com/android/messaging/ui/conversation/messagedetails/MessageDetailsViewModel.kt +++ b/src/com/android/messaging/ui/conversation/messagedetails/MessageDetailsViewModel.kt @@ -5,6 +5,7 @@ import android.content.ClipboardManager import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.android.messaging.data.appsettings.repository.AppSettingsRepository import com.android.messaging.data.conversation.repository.ConversationsRepository import com.android.messaging.ui.conversation.messagedetails.mapper.MessageDetailsUiStateMapper import com.android.messaging.ui.conversation.messagedetails.model.MessageDetailsUiState as State @@ -28,6 +29,7 @@ internal interface MessageDetailsScreenModel { @HiltViewModel internal class MessageDetailsViewModel @Inject constructor( private val conversationsRepository: ConversationsRepository, + private val appSettingsRepository: AppSettingsRepository, private val messageDetailsUiStateMapper: MessageDetailsUiStateMapper, private val clipboardManager: ClipboardManager, private val savedStateHandle: SavedStateHandle, @@ -94,6 +96,7 @@ internal class MessageDetailsViewModel @Inject constructor( return messageDetailsUiStateMapper.map( message = result?.message, details = result?.details, + isYouTubePreviewEnabled = appSettingsRepository.isYouTubeLinkPreviewsEnabled(), ) } diff --git a/src/com/android/messaging/ui/conversation/messagedetails/mapper/MessageDetailsUiStateMapper.kt b/src/com/android/messaging/ui/conversation/messagedetails/mapper/MessageDetailsUiStateMapper.kt index abdb983dd..c08fbf6ed 100644 --- a/src/com/android/messaging/ui/conversation/messagedetails/mapper/MessageDetailsUiStateMapper.kt +++ b/src/com/android/messaging/ui/conversation/messagedetails/mapper/MessageDetailsUiStateMapper.kt @@ -10,6 +10,7 @@ internal interface MessageDetailsUiStateMapper { fun map( message: ConversationMessageData?, details: ConversationMessageDetails?, + isYouTubePreviewEnabled: Boolean, ): MessageDetailsUiState } @@ -20,13 +21,17 @@ internal class MessageDetailsUiStateMapperImpl @Inject constructor( override fun map( message: ConversationMessageData?, details: ConversationMessageDetails?, + isYouTubePreviewEnabled: Boolean, ): MessageDetailsUiState { if (message == null || details == null) { return MessageDetailsUiState.Unavailable } return MessageDetailsUiState.Content( - preview = conversationMessageUiModelMapper.map(data = message), + preview = conversationMessageUiModelMapper.map( + data = message, + isYouTubePreviewEnabled = isYouTubePreviewEnabled, + ), details = details, ) } diff --git a/src/com/android/messaging/ui/conversation/messages/delegate/ConversationMessagesDelegate.kt b/src/com/android/messaging/ui/conversation/messages/delegate/ConversationMessagesDelegate.kt index 1be397239..2df404f7a 100644 --- a/src/com/android/messaging/ui/conversation/messages/delegate/ConversationMessagesDelegate.kt +++ b/src/com/android/messaging/ui/conversation/messages/delegate/ConversationMessagesDelegate.kt @@ -1,6 +1,7 @@ package com.android.messaging.ui.conversation.messages.delegate import androidx.core.net.toUri +import com.android.messaging.data.appsettings.repository.AppSettingsRepository import com.android.messaging.data.conversation.model.attachment.ConversationVCardAttachmentMetadata import com.android.messaging.data.conversation.repository.ConversationVCardMetadataRepository import com.android.messaging.data.conversation.repository.ConversationsRepository @@ -29,11 +30,13 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch internal interface ConversationMessagesDelegate : @@ -49,6 +52,7 @@ internal interface ConversationMessagesDelegate : internal class ConversationMessagesDelegateImpl @Inject constructor( private val conversationsRepository: ConversationsRepository, + private val appSettingsRepository: AppSettingsRepository, private val resolveInitialPhotoOccurrenceIndex: ResolveConversationPhotoViewerInitialOccurrenceIndex, private val conversationMessageUiModelMapper: ConversationMessageUiModelMapper, @@ -127,17 +131,24 @@ internal class ConversationMessagesDelegateImpl @Inject constructor( private fun observeConversationMessagesUiState( conversationId: String, ): Flow { - return conversationsRepository - .getConversationMessages(conversationId = conversationId) - .onEach { messages -> - currentMessages.value = messages - } - .map { messages -> - messages - .asSequence() - .map(conversationMessageUiModelMapper::map) - .toImmutableList() - } + return combine( + conversationsRepository + .getConversationMessages(conversationId = conversationId) + .onEach { messages -> + currentMessages.value = messages + }, + observeYouTubeLinkPreviewsEnabled(), + ) { messages, isYouTubePreviewEnabled -> + messages + .asSequence() + .map { data -> + conversationMessageUiModelMapper.map( + data = data, + isYouTubePreviewEnabled = isYouTubePreviewEnabled, + ) + } + .toImmutableList() + } .flatMapLatest { messages -> observeConversationMessagesUiState( messages = messages, @@ -146,6 +157,13 @@ internal class ConversationMessagesDelegateImpl @Inject constructor( .flowOn(defaultDispatcher) } + private fun observeYouTubeLinkPreviewsEnabled(): Flow { + return refreshTriggers + .onStart { emit(Unit) } + .map { appSettingsRepository.isYouTubeLinkPreviewsEnabled() } + .distinctUntilChanged() + } + private fun observeConversationMessagesUiState( messages: List, ): Flow { diff --git a/src/com/android/messaging/ui/conversation/messages/mapper/ConversationMessageUiModelMapper.kt b/src/com/android/messaging/ui/conversation/messages/mapper/ConversationMessageUiModelMapper.kt index a9098cfed..bae9b886e 100644 --- a/src/com/android/messaging/ui/conversation/messages/mapper/ConversationMessageUiModelMapper.kt +++ b/src/com/android/messaging/ui/conversation/messages/mapper/ConversationMessageUiModelMapper.kt @@ -15,14 +15,20 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList internal interface ConversationMessageUiModelMapper { - fun map(data: ConversationMessageData): ConversationMessageUiModel + fun map( + data: ConversationMessageData, + isYouTubePreviewEnabled: Boolean, + ): ConversationMessageUiModel } internal class ConversationMessageUiModelMapperImpl @Inject constructor( private val conversationVCardAttachmentUiModelMapper: ConversationVCardAttachmentUiModelMapper, ) : ConversationMessageUiModelMapper { - override fun map(data: ConversationMessageData): ConversationMessageUiModel { + override fun map( + data: ConversationMessageData, + isYouTubePreviewEnabled: Boolean, + ): ConversationMessageUiModel { return ConversationMessageUiModel( messageId = data.messageId ?: "", conversationId = data.conversationId ?: "", @@ -57,6 +63,7 @@ internal class ConversationMessageUiModelMapperImpl @Inject constructor( canForwardMessage = data.canForwardMessage, canResendMessage = data.showResendMessage, canSaveAttachments = canSaveAttachments(data), + isYouTubePreviewEnabled = isYouTubePreviewEnabled, mmsDownload = mapMmsDownload(data = data), mmsSubject = data.mmsSubject, protocol = mapProtocol(data), diff --git a/src/com/android/messaging/ui/conversation/messages/model/message/ConversationMessageUiModel.kt b/src/com/android/messaging/ui/conversation/messages/model/message/ConversationMessageUiModel.kt index 5560ef2ef..b68d40d39 100644 --- a/src/com/android/messaging/ui/conversation/messages/model/message/ConversationMessageUiModel.kt +++ b/src/com/android/messaging/ui/conversation/messages/model/message/ConversationMessageUiModel.kt @@ -31,6 +31,7 @@ internal data class ConversationMessageUiModel( val canForwardMessage: Boolean, val canResendMessage: Boolean, val canSaveAttachments: Boolean, + val isYouTubePreviewEnabled: Boolean, val mmsDownload: MmsDownloadUiModel?, val mmsSubject: String?, val protocol: Protocol, diff --git a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessage.kt b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessage.kt index bea920f8a..0bc260daa 100644 --- a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessage.kt +++ b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessage.kt @@ -215,6 +215,7 @@ private fun rememberConversationMessageContent( message.text, message.mmsSubject, message.parts, + message.isYouTubePreviewEnabled, subjectText, ) { buildConversationMessageContent( diff --git a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageContentBuilder.kt b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageContentBuilder.kt index 7aeb16311..6ab5329a9 100644 --- a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageContentBuilder.kt +++ b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageContentBuilder.kt @@ -60,7 +60,7 @@ private fun buildConversationMessageAttachments( attachment.part is ConversationMessagePartUiModel.Attachment.Image } - if (hasImageAttachment) { + if (!message.isYouTubePreviewEnabled || hasImageAttachment) { return attachmentItems } diff --git a/src/com/android/messaging/ui/conversation/preview/ConversationPreviewData.kt b/src/com/android/messaging/ui/conversation/preview/ConversationPreviewData.kt index 9ae96f745..d8ebe9244 100644 --- a/src/com/android/messaging/ui/conversation/preview/ConversationPreviewData.kt +++ b/src/com/android/messaging/ui/conversation/preview/ConversationPreviewData.kt @@ -537,6 +537,7 @@ private fun previewMessage( canForwardMessage = true, canResendMessage = status == ConversationMessageUiModel.Status.Outgoing.Failed, canSaveAttachments = canSaveAttachments, + isYouTubePreviewEnabled = true, mmsDownload = mmsDownload, mmsSubject = null, protocol = protocol,