diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fd920c020e..e54f89f97d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -35,6 +35,7 @@ + () private val observeMailFeature = mockk() + private val npsEnabled = mockk>() - private val sut = ObserveNPSEligibility( - observePrimaryUser, - observeMailFeature - ) + private val sut: ObserveNPSEligibility + get() = ObserveNPSEligibility( + observePrimaryUser, + observeMailFeature, + npsEnabled.get() + ) private val user = UserSample.Primary @Test fun `should emit false when primary user is null`() = runTest { every { observePrimaryUser() } returns flowOf(null) + expectNPSEnabled(true) sut().test { assertEquals(false, awaitItem()) awaitComplete() @@ -53,9 +58,20 @@ internal class ObserveNPSEligibilityTest { } @Test - fun `should emit false when mail feature flag is disabled`() = runTest { + fun `should emit false when FF disabled`() = runTest { every { observePrimaryUser() } returns flowOf(user) - isFFEnabled(false) + expectNPSEnabled(false) + sut().test { + assertEquals(false, awaitItem()) + awaitComplete() + } + } + + @Test + fun `should emit false when per-user feature flag is disabled`() = runTest { + every { observePrimaryUser() } returns flowOf(user) + expectPerUserFlagEnabled(false) + expectNPSEnabled(true) sut().test { assertEquals(false, awaitItem()) @@ -66,7 +82,8 @@ internal class ObserveNPSEligibilityTest { @Test fun `should emit true when feature enabled`() = runTest { every { observePrimaryUser() } returns flowOf(user) - isFFEnabled(true) + expectPerUserFlagEnabled(true) + expectNPSEnabled(true) sut().test { assertEquals(true, awaitItem()) @@ -74,8 +91,12 @@ internal class ObserveNPSEligibilityTest { } } - private fun isFFEnabled(enabled: Boolean) { + private fun expectPerUserFlagEnabled(enabled: Boolean) { every { observeMailFeature(user.userId, MailFeatureId.NPSFeedback) } returns flowOf(FeatureFlag.default("ff1", defaultValue = enabled)) } + + private fun expectNPSEnabled(value: Boolean) { + every { npsEnabled.get() } returns value + } } diff --git a/mail-upselling/dagger/src/main/kotlin/ch/protonmail/android/mailupselling/dagger/UpsellingModule.kt b/mail-upselling/dagger/src/main/kotlin/ch/protonmail/android/mailupselling/dagger/UpsellingModule.kt index fec4a147cf..5385305bc0 100644 --- a/mail-upselling/dagger/src/main/kotlin/ch/protonmail/android/mailupselling/dagger/UpsellingModule.kt +++ b/mail-upselling/dagger/src/main/kotlin/ch/protonmail/android/mailupselling/dagger/UpsellingModule.kt @@ -29,6 +29,7 @@ import ch.protonmail.android.mailupselling.domain.annotations.DriveSpotlightEnab import ch.protonmail.android.mailupselling.domain.annotations.ForceOneClickUpsellingDetailsOverride import ch.protonmail.android.mailupselling.domain.annotations.HeaderUpsellSocialProofLayoutEnabled import ch.protonmail.android.mailupselling.domain.annotations.HeaderUpsellVariantLayoutEnabled +import ch.protonmail.android.mailupselling.domain.annotations.NPSEnabled import ch.protonmail.android.mailupselling.domain.annotations.OneClickUpsellingAlwaysShown import ch.protonmail.android.mailupselling.domain.annotations.OneClickUpsellingTelemetryEnabled import ch.protonmail.android.mailupselling.domain.annotations.SidebarUpsellingEnabled @@ -48,6 +49,7 @@ import ch.protonmail.android.mailupselling.domain.usecase.featureflags.AlwaysSho import ch.protonmail.android.mailupselling.domain.usecase.featureflags.IsDriveSpotlightEnabled import ch.protonmail.android.mailupselling.domain.usecase.featureflags.IsHeaderUpsellSocialProofLayoutEnabled import ch.protonmail.android.mailupselling.domain.usecase.featureflags.IsHeaderUpsellVariantLayoutEnabled +import ch.protonmail.android.mailupselling.domain.usecase.featureflags.IsNPSEnabled import ch.protonmail.android.mailupselling.domain.usecase.featureflags.IsOneClickUpsellingTelemetryEnabled import ch.protonmail.android.mailupselling.domain.usecase.featureflags.IsSidebarUpsellingEnabled import ch.protonmail.android.mailupselling.domain.usecase.featureflags.IsSignupPaidPlanSupportEnabled @@ -101,6 +103,10 @@ object UpsellingModule { @DriveSpotlightEnabled fun provideDriveSpotlightEnabled(isEnabled: IsDriveSpotlightEnabled) = isEnabled(null) + @Provides + @NPSEnabled + fun provideNPSEnabled(isEnabled: IsNPSEnabled) = isEnabled(null) + @Provides @HeaderUpsellSocialProofLayoutEnabled fun provideUpsellSocialProofLayoutEnabled(isEnabled: IsHeaderUpsellSocialProofLayoutEnabled) = isEnabled(null) diff --git a/mail-upselling/domain/src/main/kotlin/ch/protonmail/android/mailupselling/domain/annotations/NPSEnabled.kt b/mail-upselling/domain/src/main/kotlin/ch/protonmail/android/mailupselling/domain/annotations/NPSEnabled.kt new file mode 100644 index 0000000000..7b9934c7a6 --- /dev/null +++ b/mail-upselling/domain/src/main/kotlin/ch/protonmail/android/mailupselling/domain/annotations/NPSEnabled.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022 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.mailupselling.domain.annotations + +import javax.inject.Qualifier + +/** + * Indicates whether the NPS is enabled. If disabled, individual user' feature flags will not be checked further. + */ +@Qualifier +annotation class NPSEnabled diff --git a/mail-upselling/domain/src/main/kotlin/ch/protonmail/android/mailupselling/domain/repository/GetInstalledProtonApps.kt b/mail-upselling/domain/src/main/kotlin/ch/protonmail/android/mailupselling/domain/repository/GetInstalledProtonApps.kt index fc9eba87b1..56a1689c5c 100644 --- a/mail-upselling/domain/src/main/kotlin/ch/protonmail/android/mailupselling/domain/repository/GetInstalledProtonApps.kt +++ b/mail-upselling/domain/src/main/kotlin/ch/protonmail/android/mailupselling/domain/repository/GetInstalledProtonApps.kt @@ -57,5 +57,6 @@ private val ProtonPackages = listOf( "me.proton.android.drive", "me.proton.android.calendar", "proton.android.pass", - "me.proton.wallet.android" + "me.proton.wallet.android", + "me.proton.android.lumo" ) diff --git a/mail-upselling/domain/src/main/kotlin/ch/protonmail/android/mailupselling/domain/usecase/featureflags/IsNPSEnabled.kt b/mail-upselling/domain/src/main/kotlin/ch/protonmail/android/mailupselling/domain/usecase/featureflags/IsNPSEnabled.kt new file mode 100644 index 0000000000..cf9d5584d6 --- /dev/null +++ b/mail-upselling/domain/src/main/kotlin/ch/protonmail/android/mailupselling/domain/usecase/featureflags/IsNPSEnabled.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 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.mailupselling.domain.usecase.featureflags + +import me.proton.core.domain.entity.UserId +import me.proton.core.featureflag.domain.ExperimentalProtonFeatureFlag +import me.proton.core.featureflag.domain.FeatureFlagManager +import me.proton.core.featureflag.domain.entity.FeatureId +import javax.inject.Inject + +class IsNPSEnabled @Inject constructor( + private val featureFlagManager: FeatureFlagManager +) { + + @OptIn(ExperimentalProtonFeatureFlag::class) + operator fun invoke(userId: UserId?) = featureFlagManager.getValue(userId, FeatureId(FeatureFlagId)) + + private companion object { + + const val FeatureFlagId = "MailAndroidNpsFeedback" + } +} diff --git a/mail-upselling/domain/src/test/kotlin/ch/protonmail/android/mailupselling/domain/repository/GetInstalledProtonAppsTest.kt b/mail-upselling/domain/src/test/kotlin/ch/protonmail/android/mailupselling/domain/repository/GetInstalledProtonAppsTest.kt index d4e07bc130..bd07060b54 100644 --- a/mail-upselling/domain/src/test/kotlin/ch/protonmail/android/mailupselling/domain/repository/GetInstalledProtonAppsTest.kt +++ b/mail-upselling/domain/src/test/kotlin/ch/protonmail/android/mailupselling/domain/repository/GetInstalledProtonAppsTest.kt @@ -121,7 +121,8 @@ internal class GetInstalledProtonAppsTest { "me.proton.android.drive" to "v2", "me.proton.android.calendar" to "v3", "proton.android.pass" to "v4", - "me.proton.wallet.android" to "v5" + "me.proton.wallet.android" to "v5", + "me.proton.android.lumo" to "v6" ) mockInstalled(*all.toTypedArray()) diff --git a/mail-upselling/presentation/src/main/kotlin/ch/protonmail/android/mailupselling/presentation/usecase/ObserveNPSEligibility.kt b/mail-upselling/presentation/src/main/kotlin/ch/protonmail/android/mailupselling/presentation/usecase/ObserveNPSEligibility.kt index a4ddeb93bd..2fe3f3c658 100644 --- a/mail-upselling/presentation/src/main/kotlin/ch/protonmail/android/mailupselling/presentation/usecase/ObserveNPSEligibility.kt +++ b/mail-upselling/presentation/src/main/kotlin/ch/protonmail/android/mailupselling/presentation/usecase/ObserveNPSEligibility.kt @@ -21,6 +21,7 @@ package ch.protonmail.android.mailupselling.presentation.usecase import ch.protonmail.android.mailcommon.domain.MailFeatureId import ch.protonmail.android.mailcommon.domain.usecase.ObserveMailFeature import ch.protonmail.android.mailcommon.domain.usecase.ObservePrimaryUser +import ch.protonmail.android.mailupselling.domain.annotations.DriveSpotlightEnabled import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest @@ -30,12 +31,14 @@ import javax.inject.Inject class ObserveNPSEligibility @Inject constructor( private val observePrimaryUser: ObservePrimaryUser, - private val observeMailFeature: ObserveMailFeature + private val observeMailFeature: ObserveMailFeature, + @DriveSpotlightEnabled private val driveSpotlightEnabled: Boolean ) { operator fun invoke(): Flow = observePrimaryUser() .distinctUntilChanged() .flatMapLatest { user -> if (user == null) return@flatMapLatest flowOf(false) + if (!driveSpotlightEnabled) return@flatMapLatest flowOf(false) observeMailFeature(user.userId, MailFeatureId.NPSFeedback).map { npsFeatureFlag -> npsFeatureFlag.value }