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..835beccab 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 @@ -170,6 +170,32 @@ class AppSettingsScreenTest { } } + @Test + fun privacySettings_clickDelegatesToCallback() { + var privacyClicks = 0 + + composeTestRule.setContent { + AppTheme { + AppSettingsScreen( + appSettings = AppSettingsUiState(), + onAction = screenModel::onAction, + onNavigateBack = {}, + onPrivacyClick = { privacyClicks += 1 }, + ) + } + } + + val privacyTitle = composeTestRule.activity.getString( + R.string.privacy_settings_activity_title, + ) + composeTestRule.onNodeWithText(privacyTitle).assertIsDisplayed() + composeTestRule.onNodeWithText(privacyTitle).performClick() + + composeTestRule.runOnIdle { + assertEquals(1, privacyClicks) + } + } + @Test fun advancedSettings_shownWhenTopLevel() { var advancedClicks = 0 @@ -180,6 +206,7 @@ class AppSettingsScreenTest { appSettings = AppSettingsUiState(), onAction = screenModel::onAction, onNavigateBack = {}, + onPrivacyClick = {}, isTopLevel = true, onAdvancedClick = { advancedClicks += 1 }, ) @@ -203,6 +230,7 @@ class AppSettingsScreenTest { appSettings = AppSettingsUiState(), onAction = screenModel::onAction, onNavigateBack = {}, + onPrivacyClick = {}, isTopLevel = false, onAdvancedClick = null, ) @@ -226,6 +254,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/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/appsettings/screen/SettingsViewModelTest.kt b/app/src/test/kotlin/com/android/messaging/ui/appsettings/screen/SettingsViewModelTest.kt index b2af13169..4d6a7ee11 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/appsettings/screen/SettingsViewModelTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/appsettings/screen/SettingsViewModelTest.kt @@ -127,6 +127,20 @@ class SettingsViewModelTest { } } + @Test + fun onYouTubeLinkPreviewsChanged_delegatesToAppSettings() { + runTest(context = mainDispatcherRule.testDispatcher) { + val appDelegate = mockAppSettingsDelegate() + val viewModel = createViewModel(appSettingsDelegate = appDelegate) + + viewModel.onAction(Action.YouTubeLinkPreviewsChanged(enabled = true)) + + verify(exactly = 1) { + appDelegate.onYouTubeLinkPreviewsChanged(enabled = true) + } + } + } + @Test fun onDumpSmsChanged_delegatesToAppSettings() { runTest(context = mainDispatcherRule.testDispatcher) { 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..a1dc171eb 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, + youTubeLinkPreviewsEnabled = false, ) } returns content @@ -79,6 +84,7 @@ internal class MessageDetailsViewModelTest { messageDetailsUiStateMapper.map( message = message, details = details, + youTubeLinkPreviewsEnabled = false, ) } } @@ -96,6 +102,7 @@ internal class MessageDetailsViewModelTest { messageDetailsUiStateMapper.map( message = null, details = null, + youTubeLinkPreviewsEnabled = 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..18e74ff73 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,15 +25,21 @@ internal class MessageDetailsUiStateMapperImplTest { val message = mockk() val preview = mockk() - every { conversationMessageUiModelMapper.map(data = message) } returns preview + every { + conversationMessageUiModelMapper.map( + data = message, + ) + } returns preview val result = mapper.map( message = message, details = details(), + youTubeLinkPreviewsEnabled = true, ) val content = result as MessageDetailsUiState.Content assertSame(preview, content.preview) + assertEquals(true, content.youTubeLinkPreviewsEnabled) } @Test @@ -41,6 +47,7 @@ internal class MessageDetailsUiStateMapperImplTest { val result = mapper.map( message = null, details = details(), + youTubeLinkPreviewsEnabled = false, ) assertEquals(MessageDetailsUiState.Unavailable, result) @@ -51,6 +58,7 @@ internal class MessageDetailsUiStateMapperImplTest { val result = mapper.map( message = mockk(), details = null, + youTubeLinkPreviewsEnabled = 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..b9a495563 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,11 @@ 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, + ) + } returns uiModel } } } 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..1716a598a 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,16 @@ 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], + ) + } + verify(exactly = 1) { + messageUiModelMapper.map( + data = messageData[1], + ) + } } } 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..24ad9407b --- /dev/null +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/delegate/conversationmessagesdelegate/ConversationMessagesDelegateYouTubePreviewTest.kt @@ -0,0 +1,123 @@ +package com.android.messaging.ui.conversation.messages.delegate.conversationmessagesdelegate + +import com.android.messaging.data.conversation.model.attachment.ConversationVCardAttachmentMetadata +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.ConversationMessagePartUiModel +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.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch +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_exposesUpdatedYouTubeLinkPreviewsPreferenceWithoutRemappingMessages() { + runTest(context = mainDispatcherRule.testDispatcher) { + val messageData = mockk() + val message = messageUiModel(messageId = "message-1") + coEvery { + appSettingsRepository.isYouTubeLinkPreviewsEnabled() + } returnsMany listOf(false, true) + every { messageUiModelMapper.map(data = messageData) } returns message + givenConversationMessages(messages = flowOf(listOf(messageData))) + + val delegate = createBoundDelegate( + conversationIdFlow = MutableStateFlow(CONVERSATION_ID), + ) + runCurrent() + + assertEquals( + ConversationMessagesUiState.Present( + messages = persistentListOf(message), + youTubeLinkPreviewsEnabled = false, + ), + delegate.state.value, + ) + + delegate.refresh() + runCurrent() + + assertEquals( + ConversationMessagesUiState.Present( + messages = persistentListOf(message), + youTubeLinkPreviewsEnabled = true, + ), + delegate.state.value, + ) + verify(exactly = 1) { messageUiModelMapper.map(data = messageData) } + } + } + + @Test + fun refresh_withChangedPreference_keepsResolvedVCardMetadata() { + runTest(context = mainDispatcherRule.testDispatcher) { + val contentUri = "content://vcard/1" + val loadingUiModel = vCardUiModel(titleText = "loading") + val loadedUiModel = vCardUiModel(titleText = "Alice") + + givenVCardMetadata( + contentUri = contentUri, + metadata = flow { + emit(ConversationVCardAttachmentMetadata.Loading) + emit(ConversationVCardAttachmentMetadata.Missing) + }, + ) + givenVCardUiModel(ConversationVCardAttachmentMetadata.Loading, loadingUiModel) + givenVCardUiModel(ConversationVCardAttachmentMetadata.Missing, loadedUiModel) + + val messageData = mockk() + every { messageUiModelMapper.map(data = messageData) } returns messageUiModel( + messageId = "message-1", + parts = listOf(vCardPart(contentUri = contentUri)), + ) + coEvery { + appSettingsRepository.isYouTubeLinkPreviewsEnabled() + } returnsMany listOf(false, true) + givenConversationMessages(messages = flowOf(listOf(messageData))) + + val delegate = createBoundDelegate( + conversationIdFlow = MutableStateFlow(CONVERSATION_ID), + ) + val vCardTitles = mutableListOf() + backgroundScope.launch { + delegate.state.collect { state -> + if (state is ConversationMessagesUiState.Present) { + vCardTitles += state.messages + .flatMap { it.parts } + .filterIsInstance() + .map { it.vCardUiModel?.titleText } + } + } + } + runCurrent() + + delegate.refresh() + runCurrent() + + assertEquals(1, vCardTitles.count { it == "loading" }) + assertEquals("Alice", vCardTitles.last()) + assertEquals( + true, + (delegate.state.value as ConversationMessagesUiState.Present) + .youTubeLinkPreviewsEnabled, + ) + } + } +} 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..da30dbf7e 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,9 @@ internal class ConversationMessageUiModelMapperStatusTest : ) statusToUiStatus.forEach { (bugleStatus, expectedUiStatus) -> - val uiModel = mapper.map(messageData(status = bugleStatus)) + val uiModel = mapper.map( + messageData(status = bugleStatus), + ) assertEquals("status=$bugleStatus", expectedUiStatus, uiModel.status) } 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..466c6d61f 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 @@ -33,6 +33,7 @@ class ConversationMessageContentBuilderTest { val content = buildConversationMessageContent( message = message, subjectText = null, + youTubeLinkPreviewsEnabled = true, ) assertNull(content.bodyText) @@ -65,6 +66,7 @@ class ConversationMessageContentBuilderTest { val content = buildConversationMessageContent( message = message, subjectText = null, + youTubeLinkPreviewsEnabled = true, ) assertNull(content.bodyText) @@ -90,6 +92,7 @@ class ConversationMessageContentBuilderTest { val content = buildConversationMessageContent( message = message, subjectText = null, + youTubeLinkPreviewsEnabled = true, ) assertEquals("See you soon", content.bodyText) @@ -110,6 +113,7 @@ class ConversationMessageContentBuilderTest { val content = buildConversationMessageContent( message = message, subjectText = null, + youTubeLinkPreviewsEnabled = true, ) assertEquals("Message body", content.bodyText) @@ -130,6 +134,7 @@ class ConversationMessageContentBuilderTest { val content = buildConversationMessageContent( message = message, subjectText = null, + youTubeLinkPreviewsEnabled = true, ) assertEquals("Ambient room tone", content.bodyText) 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..951ba9ddc --- /dev/null +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageContentBuilderYouTubePreviewTest.kt @@ -0,0 +1,111 @@ +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, + ), + subjectText = null, + youTubeLinkPreviewsEnabled = false, + ) + + assertTrue(content.attachments.isEmpty()) + } + + @Test + fun buildContentAppendsYouTubePreviewWhenEnabled() { + val content = buildConversationMessageContent( + message = message( + text = MESSAGE_TEXT_WITH_YOUTUBE_LINK, + ), + subjectText = null, + youTubeLinkPreviewsEnabled = true, + ) + + 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, + 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, + youTubeLinkPreviewsEnabled = true, + ) + + assertTrue( + content.attachments.none { attachment -> + attachment is ConversationMessageAttachment.YouTubePreview + }, + ) + } + + private fun message( + text: String, + 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, + 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/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..a7f597d20 100644 --- a/src/com/android/messaging/ui/appsettings/screen/SettingsViewModel.kt +++ b/src/com/android/messaging/ui/appsettings/screen/SettingsViewModel.kt @@ -71,39 +71,8 @@ internal class SettingsViewModel @Inject constructor( override fun onAction(action: Action) { when (action) { - is Action.AutoRetrieveMmsChanged -> { - subscriptionSettingsDelegate.onAutoRetrieveMmsChanged( - subId = action.subId, - enabled = action.enabled, - ) - } - - is Action.AutoRetrieveMmsWhenRoamingChanged -> { - subscriptionSettingsDelegate.onAutoRetrieveMmsWhenRoamingChanged( - subId = action.subId, - enabled = action.enabled, - ) - } - - is Action.DeliveryReportsChanged -> { - subscriptionSettingsDelegate.onDeliveryReportsChanged( - subId = action.subId, - enabled = action.enabled, - ) - } - - is Action.GroupMmsChanged -> { - subscriptionSettingsDelegate.onGroupMmsChanged( - subId = action.subId, - enabled = action.enabled, - ) - } - - is Action.PhoneNumberChanged -> { - subscriptionSettingsDelegate.onPhoneNumberChanged( - subId = action.subId, - phoneNumber = action.phoneNumber, - ) + is Action.Subscription -> { + onSubscriptionAction(action) } is Action.WirelessAlertsClicked -> { @@ -122,6 +91,10 @@ internal class SettingsViewModel @Inject constructor( appSettingsDelegate.onSendSoundChanged(action.enabled) } + is Action.YouTubeLinkPreviewsChanged -> { + appSettingsDelegate.onYouTubeLinkPreviewsChanged(action.enabled) + } + is Action.DefaultSmsAppClicked -> { val effect = if (action.isCurrentlyDefault) { Effect.OpenManageDefaultApps @@ -141,6 +114,45 @@ internal class SettingsViewModel @Inject constructor( } } + private fun onSubscriptionAction(action: Action.Subscription) { + when (action) { + is Action.AutoRetrieveMmsChanged -> { + subscriptionSettingsDelegate.onAutoRetrieveMmsChanged( + subId = action.subId, + enabled = action.enabled, + ) + } + + is Action.AutoRetrieveMmsWhenRoamingChanged -> { + subscriptionSettingsDelegate.onAutoRetrieveMmsWhenRoamingChanged( + subId = action.subId, + enabled = action.enabled, + ) + } + + is Action.DeliveryReportsChanged -> { + subscriptionSettingsDelegate.onDeliveryReportsChanged( + subId = action.subId, + enabled = action.enabled, + ) + } + + is Action.GroupMmsChanged -> { + subscriptionSettingsDelegate.onGroupMmsChanged( + subId = action.subId, + enabled = action.enabled, + ) + } + + is Action.PhoneNumberChanged -> { + subscriptionSettingsDelegate.onPhoneNumberChanged( + subId = action.subId, + phoneNumber = action.phoneNumber, + ) + } + } + } + private fun emitEffect(effect: Effect) { viewModelScope.launch { _effects.emit(effect) 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..31a0e6e3d 100644 --- a/src/com/android/messaging/ui/appsettings/screen/model/SettingsAction.kt +++ b/src/com/android/messaging/ui/appsettings/screen/model/SettingsAction.kt @@ -2,30 +2,32 @@ package com.android.messaging.ui.appsettings.screen.model internal sealed interface SettingsAction { + sealed interface Subscription : SettingsAction + data class AutoRetrieveMmsChanged( val subId: Int, val enabled: Boolean, - ) : SettingsAction + ) : Subscription data class AutoRetrieveMmsWhenRoamingChanged( val subId: Int, val enabled: Boolean, - ) : SettingsAction + ) : Subscription data class DeliveryReportsChanged( val subId: Int, val enabled: Boolean, - ) : SettingsAction + ) : Subscription data class GroupMmsChanged( val subId: Int, val enabled: Boolean, - ) : SettingsAction + ) : Subscription data class PhoneNumberChanged( val subId: Int, val phoneNumber: String, - ) : SettingsAction + ) : Subscription data class WirelessAlertsClicked( val subId: Int, @@ -43,6 +45,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/MessageDetailsComponents.kt b/src/com/android/messaging/ui/conversation/messagedetails/MessageDetailsComponents.kt index e22057773..4c3e53dd8 100644 --- a/src/com/android/messaging/ui/conversation/messagedetails/MessageDetailsComponents.kt +++ b/src/com/android/messaging/ui/conversation/messagedetails/MessageDetailsComponents.kt @@ -42,6 +42,7 @@ internal fun MessageDetailsPreviewCard( preview: ConversationMessageUiModel, details: ConversationMessageDetails, modifier: Modifier = Modifier, + youTubeLinkPreviewsEnabled: Boolean = false, ) { val simDisplayName = details.subscriptionLabel?.resolveDisplayName() @@ -55,6 +56,7 @@ internal fun MessageDetailsPreviewCard( message = preview, simDisplayName = simDisplayName, showIncomingParticipantIdentity = false, + youTubeLinkPreviewsEnabled = youTubeLinkPreviewsEnabled, ) } } diff --git a/src/com/android/messaging/ui/conversation/messagedetails/MessageDetailsScreen.kt b/src/com/android/messaging/ui/conversation/messagedetails/MessageDetailsScreen.kt index 75e3bb6e3..a719aa6a6 100644 --- a/src/com/android/messaging/ui/conversation/messagedetails/MessageDetailsScreen.kt +++ b/src/com/android/messaging/ui/conversation/messagedetails/MessageDetailsScreen.kt @@ -132,6 +132,7 @@ private fun MessageDetailsContent( MessageDetailsPreviewCard( preview = content.preview, details = details, + youTubeLinkPreviewsEnabled = content.youTubeLinkPreviewsEnabled, ) MessageDetailsMessageFields( diff --git a/src/com/android/messaging/ui/conversation/messagedetails/MessageDetailsViewModel.kt b/src/com/android/messaging/ui/conversation/messagedetails/MessageDetailsViewModel.kt index 083510340..6dd8af277 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, + youTubeLinkPreviewsEnabled = 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..a1dd84d14 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?, + youTubeLinkPreviewsEnabled: Boolean, ): MessageDetailsUiState } @@ -20,6 +21,7 @@ internal class MessageDetailsUiStateMapperImpl @Inject constructor( override fun map( message: ConversationMessageData?, details: ConversationMessageDetails?, + youTubeLinkPreviewsEnabled: Boolean, ): MessageDetailsUiState { if (message == null || details == null) { return MessageDetailsUiState.Unavailable @@ -28,6 +30,7 @@ internal class MessageDetailsUiStateMapperImpl @Inject constructor( return MessageDetailsUiState.Content( preview = conversationMessageUiModelMapper.map(data = message), details = details, + youTubeLinkPreviewsEnabled = youTubeLinkPreviewsEnabled, ) } } diff --git a/src/com/android/messaging/ui/conversation/messagedetails/model/MessageDetailsUiState.kt b/src/com/android/messaging/ui/conversation/messagedetails/model/MessageDetailsUiState.kt index 66f79e0ef..aee906110 100644 --- a/src/com/android/messaging/ui/conversation/messagedetails/model/MessageDetailsUiState.kt +++ b/src/com/android/messaging/ui/conversation/messagedetails/model/MessageDetailsUiState.kt @@ -15,5 +15,6 @@ internal sealed interface MessageDetailsUiState { data class Content( val preview: ConversationMessageUiModel, val details: ConversationMessageDetails, + val youTubeLinkPreviewsEnabled: Boolean = false, ) : MessageDetailsUiState } 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..af38ae247 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,28 +131,41 @@ 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() - } - .flatMapLatest { messages -> - observeConversationMessagesUiState( - messages = messages, - ) - } + return combine( + conversationsRepository + .getConversationMessages(conversationId = conversationId) + .onEach { messages -> + currentMessages.value = messages + } + .map { messages -> + messages + .asSequence() + .map(conversationMessageUiModelMapper::map) + .toImmutableList() + } + .flatMapLatest { messages -> + observeMessagesWithVCardMetadata(messages = messages) + }, + observeYouTubeLinkPreviewsEnabled(), + ) { messages, youTubeLinkPreviewsEnabled -> + ConversationMessagesUiState.Present( + messages = messages, + youTubeLinkPreviewsEnabled = youTubeLinkPreviewsEnabled, + ) + } .flowOn(defaultDispatcher) } - private fun observeConversationMessagesUiState( + private fun observeYouTubeLinkPreviewsEnabled(): Flow { + return refreshTriggers + .onStart { emit(Unit) } + .map { appSettingsRepository.isYouTubeLinkPreviewsEnabled() } + .distinctUntilChanged() + } + + private fun observeMessagesWithVCardMetadata( messages: List, - ): Flow { + ): Flow> { val vCardContentUris = messages .asSequence() .flatMap { message -> message.parts.asSequence() } @@ -161,11 +178,7 @@ internal class ConversationMessagesDelegateImpl @Inject constructor( .toList() if (vCardContentUris.isEmpty()) { - return flowOf( - ConversationMessagesUiState.Present( - messages = messages.toImmutableList(), - ), - ) + return flowOf(messages.toImmutableList()) } val vCardMetadataFlows = vCardContentUris.map { contentUri -> @@ -184,11 +197,9 @@ internal class ConversationMessagesDelegateImpl @Inject constructor( pair.first to pair.second } - ConversationMessagesUiState.Present( - messages = updateMessagesWithVCardUiModel( - messages = messages, - vCardAttachmentMetadata = vCardAttachmentMetadata, - ), + updateMessagesWithVCardUiModel( + messages = messages, + vCardAttachmentMetadata = vCardAttachmentMetadata, ) } } diff --git a/src/com/android/messaging/ui/conversation/messages/model/message/ConversationMessagesUiState.kt b/src/com/android/messaging/ui/conversation/messages/model/message/ConversationMessagesUiState.kt index 9ac1a199f..b2b84fc28 100644 --- a/src/com/android/messaging/ui/conversation/messages/model/message/ConversationMessagesUiState.kt +++ b/src/com/android/messaging/ui/conversation/messages/model/message/ConversationMessagesUiState.kt @@ -13,5 +13,6 @@ internal sealed interface ConversationMessagesUiState { @Immutable data class Present( val messages: ImmutableList = persistentListOf(), + val youTubeLinkPreviewsEnabled: Boolean = false, ) : ConversationMessagesUiState } diff --git a/src/com/android/messaging/ui/conversation/messages/ui/ConversationMessages.kt b/src/com/android/messaging/ui/conversation/messages/ui/ConversationMessages.kt index 09aa9dfe1..f8c12ecca 100644 --- a/src/com/android/messaging/ui/conversation/messages/ui/ConversationMessages.kt +++ b/src/com/android/messaging/ui/conversation/messages/ui/ConversationMessages.kt @@ -74,6 +74,7 @@ internal fun ConversationMessages( listState: LazyListState, selectedMessageIds: ImmutableSet = persistentSetOf(), showIncomingParticipantIdentity: Boolean = true, + youTubeLinkPreviewsEnabled: Boolean = false, subscriptions: ImmutableList = persistentListOf(), currentSendSimDisplayName: String? = null, additionalTopContentPadding: Dp = 0.dp, @@ -124,6 +125,7 @@ internal fun ConversationMessages( selectedMessageIds = selectedMessageIds, isSelectionMode = isSelectionMode, showIncomingParticipantIdentity = showIncomingParticipantIdentity, + youTubeLinkPreviewsEnabled = youTubeLinkPreviewsEnabled, simDisplayNameByParticipantId = simDisplayNameByParticipantId, onAttachmentClick = onAttachmentClick, onExternalUriClick = onExternalUriClick, @@ -143,6 +145,7 @@ private fun LazyListScope.conversationMessageItems( selectedMessageIds: ImmutableSet, isSelectionMode: Boolean, showIncomingParticipantIdentity: Boolean, + youTubeLinkPreviewsEnabled: Boolean, simDisplayNameByParticipantId: ImmutableMap, onAttachmentClick: OnConversationAttachmentClick, onExternalUriClick: (String) -> Unit, @@ -173,6 +176,7 @@ private fun LazyListScope.conversationMessageItems( isSelectionMode = isSelectionMode, isSelected = selectedMessageIds.contains(message.messageId), showIncomingParticipantIdentity = showIncomingParticipantIdentity, + youTubeLinkPreviewsEnabled = youTubeLinkPreviewsEnabled, simDisplayNameByParticipantId = simDisplayNameByParticipantId, onAttachmentClick = onAttachmentClick, onExternalUriClick = onExternalUriClick, @@ -293,6 +297,7 @@ private fun ConversationMessagesItem( isSelectionMode: Boolean, isSelected: Boolean, showIncomingParticipantIdentity: Boolean, + youTubeLinkPreviewsEnabled: Boolean, simDisplayNameByParticipantId: ImmutableMap, onAttachmentClick: OnConversationAttachmentClick, onExternalUriClick: (String) -> Unit, @@ -329,6 +334,7 @@ private fun ConversationMessagesItem( isSelectionMode = isSelectionMode, message = message, showIncomingParticipantIdentity = showIncomingParticipantIdentity, + youTubeLinkPreviewsEnabled = youTubeLinkPreviewsEnabled, simDisplayName = simDisplayName, onAttachmentClick = onAttachmentClick, onExternalUriClick = onExternalUriClick, 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..0819e9ab0 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 @@ -48,6 +48,7 @@ internal fun ConversationMessage( isSelected: Boolean = false, isSelectionMode: Boolean = false, showIncomingParticipantIdentity: Boolean = true, + youTubeLinkPreviewsEnabled: Boolean = false, simDisplayName: String? = null, onAttachmentClick: OnConversationAttachmentClick = { _, _, _ -> }, @@ -66,6 +67,7 @@ internal fun ConversationMessage( val layout = rememberConversationMessageLayout( message = message, showIncomingParticipantIdentity = showIncomingParticipantIdentity, + youTubeLinkPreviewsEnabled = youTubeLinkPreviewsEnabled, ) val maxBubbleWidth = remember(maxWidth) { @@ -128,6 +130,7 @@ internal enum class ConversationMessageBubbleLayoutMode { private fun rememberConversationMessageLayout( message: ConversationMessageUiModel, showIncomingParticipantIdentity: Boolean, + youTubeLinkPreviewsEnabled: Boolean, ): ConversationMessageLayout { val bubbleShape = remember( message.canClusterWithPrevious, @@ -136,7 +139,10 @@ private fun rememberConversationMessageLayout( messageBubbleShape(message = message) } - val content = rememberConversationMessageContent(message = message) + val content = rememberConversationMessageContent( + message = message, + youTubeLinkPreviewsEnabled = youTubeLinkPreviewsEnabled, + ) val metadataText = rememberConversationMessageMetadataText(message = message) val showSender = message.isIncoming && @@ -196,6 +202,7 @@ private fun conversationMessageMaxBubbleWidth( @Composable private fun rememberConversationMessageContent( message: ConversationMessageUiModel, + youTubeLinkPreviewsEnabled: Boolean, ): ConversationMessageContent { val resources = LocalResources.current val configuration = LocalConfiguration.current @@ -215,11 +222,13 @@ private fun rememberConversationMessageContent( message.text, message.mmsSubject, message.parts, + youTubeLinkPreviewsEnabled, subjectText, ) { buildConversationMessageContent( message = message, subjectText = subjectText, + youTubeLinkPreviewsEnabled = youTubeLinkPreviewsEnabled, ) } } diff --git a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageBubblePreviewSupport.kt b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageBubblePreviewSupport.kt index cf95075d4..1faeb21f9 100644 --- a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageBubblePreviewSupport.kt +++ b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageBubblePreviewSupport.kt @@ -122,6 +122,7 @@ internal fun previewConversationMessageLayout( content = buildConversationMessageContent( message = message, subjectText = message.mmsSubject, + youTubeLinkPreviewsEnabled = true, ), metadataText = "18:04", showSender = showSender, 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..c1c74c5de 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 @@ -16,8 +16,12 @@ import kotlinx.collections.immutable.toImmutableList internal fun buildConversationMessageContent( message: ConversationMessageUiModel, subjectText: String?, + youTubeLinkPreviewsEnabled: Boolean, ): ConversationMessageContent { - val attachments = buildConversationMessageAttachments(message = message) + val attachments = buildConversationMessageAttachments( + message = message, + youTubeLinkPreviewsEnabled = youTubeLinkPreviewsEnabled, + ) val attachmentSections = buildConversationAttachmentSections( attachments = attachments, vCardSubtitleTextResIdOverride = vCardSubtitleTextResIdOverride(message), @@ -49,6 +53,7 @@ private fun vCardSubtitleTextResIdOverride(message: ConversationMessageUiModel): private fun buildConversationMessageAttachments( message: ConversationMessageUiModel, + youTubeLinkPreviewsEnabled: Boolean, ): ImmutableList { val attachmentItems = message .parts @@ -60,7 +65,7 @@ private fun buildConversationMessageAttachments( attachment.part is ConversationMessagePartUiModel.Attachment.Image } - if (hasImageAttachment) { + if (!youTubeLinkPreviewsEnabled || hasImageAttachment) { return attachmentItems } diff --git a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageRowsPreviewSupport.kt b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageRowsPreviewSupport.kt index d75a983e0..c173b9448 100644 --- a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageRowsPreviewSupport.kt +++ b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageRowsPreviewSupport.kt @@ -186,6 +186,7 @@ internal fun previewConversationMessageRowsLayout( val content = buildConversationMessageContent( message = message, subjectText = message.mmsSubject, + youTubeLinkPreviewsEnabled = true, ) return ConversationMessageLayout( diff --git a/src/com/android/messaging/ui/conversation/preview/ConversationPreviewData.kt b/src/com/android/messaging/ui/conversation/preview/ConversationPreviewData.kt index 9ae96f745..b6bcfd434 100644 --- a/src/com/android/messaging/ui/conversation/preview/ConversationPreviewData.kt +++ b/src/com/android/messaging/ui/conversation/preview/ConversationPreviewData.kt @@ -279,7 +279,10 @@ private fun previewVCardAvatarPhoto(): VCardAvatarPhoto { } internal fun previewMessagesUiState(): ConversationMessagesUiState.Present { - return ConversationMessagesUiState.Present(messages = previewMessages()) + return ConversationMessagesUiState.Present( + messages = previewMessages(), + youTubeLinkPreviewsEnabled = true, + ) } internal fun previewMessages(): ImmutableList { diff --git a/src/com/android/messaging/ui/conversation/screen/ConversationScreenContent.kt b/src/com/android/messaging/ui/conversation/screen/ConversationScreenContent.kt index 73afc3a8c..208c22b62 100644 --- a/src/com/android/messaging/ui/conversation/screen/ConversationScreenContent.kt +++ b/src/com/android/messaging/ui/conversation/screen/ConversationScreenContent.kt @@ -218,6 +218,7 @@ private fun ConversationScreenPresentContent( listState = messagesListState, selectedMessageIds = uiState.selection.selectedMessageIds, showIncomingParticipantIdentity = showIncomingParticipantIdentity, + youTubeLinkPreviewsEnabled = messagesState.youTubeLinkPreviewsEnabled, subscriptions = uiState.composer.simSelector.subscriptions, currentSendSimDisplayName = currentSendSimDisplayName, additionalTopContentPadding = additionalTopContentPadding,