From e75df7fe60204fee70d2e6458fc025dbaaac62fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Forlini?= Date: Mon, 19 Jan 2026 11:11:25 +0100 Subject: [PATCH] Add separate feature spotlight FF ET-5738 --- .../HomeFeatureSpotlightViewModel.kt | 25 ++++- .../HomeFeatureSpotlightViewModelTest.kt | 106 ++++++++++++++++++ .../mailfeatureflags/di/FeatureFlagsModule.kt | 13 +++ .../annotation/FeatureFlagAnnotations.kt | 4 + .../model/FeatureFlagDefinitionItems.kt | 8 ++ 5 files changed, 150 insertions(+), 6 deletions(-) create mode 100644 app/src/test/kotlin/ch/protonmail/android/feature/spotlight/HomeFeatureSpotlightViewModelTest.kt diff --git a/app/src/main/kotlin/ch/protonmail/android/feature/spotlight/HomeFeatureSpotlightViewModel.kt b/app/src/main/kotlin/ch/protonmail/android/feature/spotlight/HomeFeatureSpotlightViewModel.kt index 728a0c28db..9d88766795 100644 --- a/app/src/main/kotlin/ch/protonmail/android/feature/spotlight/HomeFeatureSpotlightViewModel.kt +++ b/app/src/main/kotlin/ch/protonmail/android/feature/spotlight/HomeFeatureSpotlightViewModel.kt @@ -20,25 +20,38 @@ package ch.protonmail.android.feature.spotlight import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import ch.protonmail.android.mailfeatureflags.domain.annotation.IsFeatureSpotlightEnabled +import ch.protonmail.android.mailfeatureflags.domain.model.FeatureFlag import ch.protonmail.android.mailspotlight.domain.usecase.ObserveFeatureSpotlightDisplay import ch.protonmail.android.mailspotlight.presentation.model.FeatureSpotlightState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import javax.inject.Inject @HiltViewModel class HomeFeatureSpotlightViewModel @Inject constructor( - observeFeatureSpotlightDisplay: ObserveFeatureSpotlightDisplay + observeFeatureSpotlightDisplay: ObserveFeatureSpotlightDisplay, + @IsFeatureSpotlightEnabled private val isEnabled: FeatureFlag ) : ViewModel() { - val state: StateFlow = observeFeatureSpotlightDisplay().map { preferenceEither -> - preferenceEither.fold( - ifLeft = { FeatureSpotlightState.Hide }, - ifRight = { if (it.show) FeatureSpotlightState.Show else FeatureSpotlightState.Hide } - ) + val state: StateFlow = flow { + if (!isEnabled.get()) { + emit(FeatureSpotlightState.Hide) + } else { + emitAll( + observeFeatureSpotlightDisplay().map { preferenceEither -> + preferenceEither.fold( + ifLeft = { FeatureSpotlightState.Hide }, + ifRight = { if (it.show) FeatureSpotlightState.Show else FeatureSpotlightState.Hide } + ) + } + ) + } }.stateIn( scope = viewModelScope, started = SharingStarted.Lazily, diff --git a/app/src/test/kotlin/ch/protonmail/android/feature/spotlight/HomeFeatureSpotlightViewModelTest.kt b/app/src/test/kotlin/ch/protonmail/android/feature/spotlight/HomeFeatureSpotlightViewModelTest.kt new file mode 100644 index 0000000000..13cb3f0c90 --- /dev/null +++ b/app/src/test/kotlin/ch/protonmail/android/feature/spotlight/HomeFeatureSpotlightViewModelTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2025 Proton Technologies AG + * This file is part of Proton Technologies AG and Proton Mail. + * + * Proton Mail is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Proton Mail is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Proton Mail. If not, see . + */ + +package ch.protonmail.android.feature.spotlight + +import app.cash.turbine.test +import arrow.core.left +import arrow.core.right +import ch.protonmail.android.mailcommon.domain.model.PreferencesError +import ch.protonmail.android.mailfeatureflags.domain.model.FeatureFlag +import ch.protonmail.android.mailspotlight.domain.model.FeatureSpotlightDisplay +import ch.protonmail.android.mailspotlight.domain.usecase.ObserveFeatureSpotlightDisplay +import ch.protonmail.android.mailspotlight.presentation.model.FeatureSpotlightState +import ch.protonmail.android.test.utils.rule.MainDispatcherRule +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import kotlin.test.Test +import kotlin.test.assertEquals + +internal class HomeFeatureSpotlightViewModelTest { + + @get:Rule + val mainDispatcherRule = MainDispatcherRule() + + private val mockFeatureFlag = mockk>() + private val mockObserveFeatureSpotlightDisplay = mockk() + + @Test + fun `should emit Hide when feature flag is disabled`() = runTest { + // Given + coEvery { mockFeatureFlag.get() } returns false + + val viewModel = buildViewModel() + + // When/Then + viewModel.state.test { + assertEquals(FeatureSpotlightState.Hide, awaitItem()) + } + } + + @Test + fun `should emit Show when feature flag is enabled and preference is show`() = runTest { + // Given + coEvery { mockFeatureFlag.get() } returns true + every { mockObserveFeatureSpotlightDisplay() } returns flowOf(FeatureSpotlightDisplay(show = true).right()) + + val viewModel = buildViewModel() + + // When/Then + viewModel.state.test { + assertEquals(FeatureSpotlightState.Show, awaitItem()) + } + } + + @Test + fun `should emit Hide when feature flag is enabled and preference is hide`() = runTest { + // Given + coEvery { mockFeatureFlag.get() } returns true + every { mockObserveFeatureSpotlightDisplay() } returns flowOf(FeatureSpotlightDisplay(show = false).right()) + + val viewModel = buildViewModel() + + // When/Then + viewModel.state.test { + assertEquals(FeatureSpotlightState.Hide, awaitItem()) + } + } + + @Test + fun `should emit Hide when feature flag is enabled and preference returns error`() = runTest { + // Given + coEvery { mockFeatureFlag.get() } returns true + every { mockObserveFeatureSpotlightDisplay() } returns flowOf(PreferencesError.left()) + + val viewModel = buildViewModel() + + // When/Then + viewModel.state.test { + assertEquals(FeatureSpotlightState.Hide, awaitItem()) + } + } + + private fun buildViewModel() = HomeFeatureSpotlightViewModel( + observeFeatureSpotlightDisplay = mockObserveFeatureSpotlightDisplay, + isEnabled = mockFeatureFlag + ) +} diff --git a/mail-featureflags/dagger/src/main/kotlin/ch/protonmail/android/mailfeatureflags/di/FeatureFlagsModule.kt b/mail-featureflags/dagger/src/main/kotlin/ch/protonmail/android/mailfeatureflags/di/FeatureFlagsModule.kt index 6a2d68a7ff..4312259b81 100644 --- a/mail-featureflags/dagger/src/main/kotlin/ch/protonmail/android/mailfeatureflags/di/FeatureFlagsModule.kt +++ b/mail-featureflags/dagger/src/main/kotlin/ch/protonmail/android/mailfeatureflags/di/FeatureFlagsModule.kt @@ -28,6 +28,7 @@ import ch.protonmail.android.mailfeatureflags.domain.annotation.FeatureFlagsCoro import ch.protonmail.android.mailfeatureflags.domain.annotation.IsBlackFridayWave1Enabled import ch.protonmail.android.mailfeatureflags.domain.annotation.IsBlackFridayWave2Enabled import ch.protonmail.android.mailfeatureflags.domain.annotation.IsDebugInspectDbEnabled +import ch.protonmail.android.mailfeatureflags.domain.annotation.IsFeatureSpotlightEnabled import ch.protonmail.android.mailfeatureflags.domain.annotation.IsInjectCssOverrideEnabled import ch.protonmail.android.mailfeatureflags.domain.annotation.IsLastMessageAutoExpandEnabled import ch.protonmail.android.mailfeatureflags.domain.annotation.IsOnboardingUpsellEnabled @@ -39,6 +40,7 @@ import ch.protonmail.android.mailfeatureflags.domain.model.ComposerAutoCollapseQ import ch.protonmail.android.mailfeatureflags.domain.model.ConversationDetailAutoExpandLastMessageEnabled import ch.protonmail.android.mailfeatureflags.domain.model.DebugInspectDbEnabled import ch.protonmail.android.mailfeatureflags.domain.model.FeatureFlagDefinition +import ch.protonmail.android.mailfeatureflags.domain.model.FeatureSpotlight import ch.protonmail.android.mailfeatureflags.domain.model.InjectDetailCssOverrideEnabled import ch.protonmail.android.mailfeatureflags.domain.model.MailBlackFriday2025Enabled import ch.protonmail.android.mailfeatureflags.domain.model.MailBlackFriday2025Wave2Enabled @@ -186,4 +188,15 @@ object FeatureFlagsModule { @IntoSet @Singleton fun providePrivacyBundle2601EnabledDef(): FeatureFlagDefinition = PrivacyBundle2601 + + + @Provides + @Singleton + @IsFeatureSpotlightEnabled + fun provideFeatureSpotlightEnabled(factory: BooleanFeatureFlagFactory) = factory.create(FeatureSpotlight.key, false) + + @Provides + @IntoSet + @Singleton + fun provideFeatureSpotlightEnabledDef(): FeatureFlagDefinition = FeatureSpotlight } diff --git a/mail-featureflags/domain/src/main/kotlin/ch/protonmail/android/mailfeatureflags/domain/annotation/FeatureFlagAnnotations.kt b/mail-featureflags/domain/src/main/kotlin/ch/protonmail/android/mailfeatureflags/domain/annotation/FeatureFlagAnnotations.kt index 70d36da2b2..ed2e80864c 100644 --- a/mail-featureflags/domain/src/main/kotlin/ch/protonmail/android/mailfeatureflags/domain/annotation/FeatureFlagAnnotations.kt +++ b/mail-featureflags/domain/src/main/kotlin/ch/protonmail/android/mailfeatureflags/domain/annotation/FeatureFlagAnnotations.kt @@ -63,3 +63,7 @@ annotation class IsShowRatingBoosterEnabled @Qualifier @Retention(AnnotationRetention.BINARY) annotation class IsPrivacyBundle2601Enabled + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class IsFeatureSpotlightEnabled diff --git a/mail-featureflags/domain/src/main/kotlin/ch/protonmail/android/mailfeatureflags/domain/model/FeatureFlagDefinitionItems.kt b/mail-featureflags/domain/src/main/kotlin/ch/protonmail/android/mailfeatureflags/domain/model/FeatureFlagDefinitionItems.kt index ee3435b77b..9b1eeec275 100644 --- a/mail-featureflags/domain/src/main/kotlin/ch/protonmail/android/mailfeatureflags/domain/model/FeatureFlagDefinitionItems.kt +++ b/mail-featureflags/domain/src/main/kotlin/ch/protonmail/android/mailfeatureflags/domain/model/FeatureFlagDefinitionItems.kt @@ -105,3 +105,11 @@ data object PrivacyBundle2601 : FeatureFlagDefinition( description = "Enable padlocks + blocked trackers in details/composer.", defaultValue = false ) + +data object FeatureSpotlight : FeatureFlagDefinition( + key = "MailAndroidV7FeatureSpotlight", + name = "Enable the feature spotlight", + category = FeatureFlagCategory.Details, + description = "Show a what's new screen on startup when available.", + defaultValue = false +)