diff --git a/.idea/saveactions_settings.xml b/.idea/saveactions_settings.xml
index 74bfed93c..d446172cc 100644
--- a/.idea/saveactions_settings.xml
+++ b/.idea/saveactions_settings.xml
@@ -13,6 +13,7 @@
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index f04e6f4cf..42a047724 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -357,6 +357,7 @@ dependencies {
`constraint-layout`,
`material`,
`paging-runtime`,
+ `google-play-review`,
// Lifecycle
`lifecycle-extensions`,
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4505582d8..4899c5ee4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -144,6 +144,11 @@
android:name="ch.protonmail.android.onboarding.base.presentation.StartOnboardingObserverInitializer"
android:value="androidx.startup"
tools:node="remove" />
+
+ {
diff --git a/app/src/main/java/ch/protonmail/android/di/ViewModelModule.kt b/app/src/main/java/ch/protonmail/android/di/ViewModelModule.kt
index b53a2124d..b93fc6b65 100644
--- a/app/src/main/java/ch/protonmail/android/di/ViewModelModule.kt
+++ b/app/src/main/java/ch/protonmail/android/di/ViewModelModule.kt
@@ -31,6 +31,7 @@ import ch.protonmail.android.contacts.groups.edit.chooser.AddressChooserViewMode
import ch.protonmail.android.core.ProtonMailApplication
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.drawer.presentation.mapper.DrawerFoldersAndLabelsSectionUiModelMapper
+import ch.protonmail.android.feature.rating.usecase.StartRateAppFlowIfNeeded
import ch.protonmail.android.labels.domain.LabelRepository
import ch.protonmail.android.labels.domain.usecase.ObserveLabels
import ch.protonmail.android.labels.domain.usecase.ObserveLabelsAndFoldersWithChildren
@@ -117,7 +118,8 @@ internal class ViewModelModule {
getMailSettings: GetMailSettings,
mailboxItemUiModelMapper: MailboxItemUiModelMapper,
fetchEventsAndReschedule: FetchEventsAndReschedule,
- clearNotificationsForUser: ClearNotificationsForUser
+ clearNotificationsForUser: ClearNotificationsForUser,
+ startRateAppFlowIfNeeded: StartRateAppFlowIfNeeded
) = MailboxViewModel(
messageDetailsRepositoryFactory = messageDetailsRepositoryFactory,
userManager = userManager,
@@ -143,6 +145,7 @@ internal class ViewModelModule {
getMailSettings = getMailSettings,
mailboxItemUiModelMapper = mailboxItemUiModelMapper,
fetchEventsAndReschedule = fetchEventsAndReschedule,
- clearNotificationsForUser = clearNotificationsForUser
+ clearNotificationsForUser = clearNotificationsForUser,
+ startRateAppFlowIfNeeded = startRateAppFlowIfNeeded
)
}
diff --git a/app/src/main/java/ch/protonmail/android/feature/rating/MailboxScreenViewInMemoryRepository.kt b/app/src/main/java/ch/protonmail/android/feature/rating/MailboxScreenViewInMemoryRepository.kt
new file mode 100644
index 000000000..848bdce2e
--- /dev/null
+++ b/app/src/main/java/ch/protonmail/android/feature/rating/MailboxScreenViewInMemoryRepository.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2022 Proton AG
+ *
+ * This file is part of 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 https://www.gnu.org/licenses/.
+ */
+
+package ch.protonmail.android.feature.rating
+
+import timber.log.Timber
+import java.util.concurrent.atomic.AtomicInteger
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class MailboxScreenViewInMemoryRepository @Inject constructor() {
+
+ public val screenViewCount: Int
+ get() = mailboxScreenViews.get()
+
+ private var mailboxScreenViews = AtomicInteger(0)
+
+ fun recordScreenView() {
+ mailboxScreenViews.incrementAndGet()
+ Timber.d("Recording mailbox screen view: count $mailboxScreenViews")
+ }
+
+}
diff --git a/app/src/main/java/ch/protonmail/android/feature/rating/StartRateAppFlow.kt b/app/src/main/java/ch/protonmail/android/feature/rating/StartRateAppFlow.kt
new file mode 100644
index 000000000..7f8ac4039
--- /dev/null
+++ b/app/src/main/java/ch/protonmail/android/feature/rating/StartRateAppFlow.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2022 Proton AG
+ *
+ * This file is part of 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 https://www.gnu.org/licenses/.
+ */
+package ch.protonmail.android.feature.rating
+
+import android.content.Context
+import com.google.android.play.core.review.ReviewManagerFactory
+import dagger.hilt.android.qualifiers.ApplicationContext
+import timber.log.Timber
+import javax.inject.Inject
+
+class StartRateAppFlow @Inject constructor(
+ @ApplicationContext
+ private val context: Context
+) {
+
+ operator fun invoke() {
+ val manager = ReviewManagerFactory.create(context)
+ manager.requestReviewFlow().addOnCompleteListener {
+ Timber.d("App review finished. Success = ${it.isSuccessful}")
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ch/protonmail/android/feature/rating/usecase/StartRateAppFlowIfNeeded.kt b/app/src/main/java/ch/protonmail/android/feature/rating/usecase/StartRateAppFlowIfNeeded.kt
new file mode 100644
index 000000000..784ca497e
--- /dev/null
+++ b/app/src/main/java/ch/protonmail/android/feature/rating/usecase/StartRateAppFlowIfNeeded.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2022 Proton AG
+ *
+ * This file is part of 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 https://www.gnu.org/licenses/.
+ */
+
+package ch.protonmail.android.feature.rating.usecase
+
+import ch.protonmail.android.feature.rating.MailboxScreenViewInMemoryRepository
+import ch.protonmail.android.feature.rating.StartRateAppFlow
+import ch.protonmail.android.featureflags.MailFeatureFlags
+import me.proton.core.domain.entity.UserId
+import me.proton.core.featureflag.domain.FeatureFlagManager
+import me.proton.core.featureflag.domain.entity.FeatureFlag
+import javax.inject.Inject
+
+class StartRateAppFlowIfNeeded @Inject constructor(
+ private val mailboxScreenViewsRepository: MailboxScreenViewInMemoryRepository,
+ private val featureFlagManager: FeatureFlagManager,
+ private val startRateAppFlow: StartRateAppFlow
+) {
+
+ suspend operator fun invoke(userId: UserId) {
+ if (!isShowReviewFeatureFlagEnabled(userId)) {
+ return
+ }
+ if (mailboxScreenViewsRepository.screenViewCount < MailboxScreenViewsThreshold) {
+ return
+ }
+ startRateAppFlow()
+ recordReviewFlowStarted(userId)
+ }
+
+ private suspend fun recordReviewFlowStarted(userId: UserId) {
+ val featureFlag = getShowReviewFeatureFlag(userId)
+ val offFeatureFlag = featureFlag.copy(defaultValue = false, value = false)
+ featureFlagManager.update(offFeatureFlag)
+ }
+
+ private suspend fun isShowReviewFeatureFlagEnabled(userId: UserId) = getShowReviewFeatureFlag(userId).value
+
+ private suspend fun getShowReviewFeatureFlag(
+ userId: UserId
+ ) = featureFlagManager.getOrDefault(
+ userId,
+ MailFeatureFlags.ShowReviewAppDialog.featureId,
+ FeatureFlag.default(MailFeatureFlags.ShowReviewAppDialog.featureId.id, false)
+ )
+
+ companion object {
+ private const val MailboxScreenViewsThreshold = 2
+ }
+}
diff --git a/app/src/main/java/ch/protonmail/android/featureflags/FeatureFlagsInitializer.kt b/app/src/main/java/ch/protonmail/android/featureflags/FeatureFlagsInitializer.kt
new file mode 100644
index 000000000..43a26bd9a
--- /dev/null
+++ b/app/src/main/java/ch/protonmail/android/featureflags/FeatureFlagsInitializer.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2022 Proton AG
+ *
+ * This file is part of 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 https://www.gnu.org/licenses/.
+ */
+package ch.protonmail.android.featureflags
+
+import android.content.Context
+import androidx.startup.Initializer
+import dagger.hilt.EntryPoint
+import dagger.hilt.InstallIn
+import dagger.hilt.android.EntryPointAccessors
+import dagger.hilt.components.SingletonComponent
+
+class FeatureFlagsInitializer : Initializer {
+
+ override fun create(context: Context): RefreshFeatureFlags {
+ val refreshFeatureFlags = EntryPointAccessors.fromApplication(
+ context.applicationContext,
+ FeatureFlagsEntryPoint::class.java
+ ).refreshFeatureFlags()
+ refreshFeatureFlags.refresh()
+ return refreshFeatureFlags
+ }
+
+ override fun dependencies(): List>> = emptyList()
+
+ @EntryPoint
+ @InstallIn(SingletonComponent::class)
+ interface FeatureFlagsEntryPoint {
+
+ fun refreshFeatureFlags(): RefreshFeatureFlags
+ }
+}
diff --git a/app/src/main/java/ch/protonmail/android/featureflags/MailFeatureFlags.kt b/app/src/main/java/ch/protonmail/android/featureflags/MailFeatureFlags.kt
new file mode 100644
index 000000000..1076d48e2
--- /dev/null
+++ b/app/src/main/java/ch/protonmail/android/featureflags/MailFeatureFlags.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2022 Proton AG
+ *
+ * This file is part of 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 https://www.gnu.org/licenses/.
+ */
+
+package ch.protonmail.android.featureflags
+
+import me.proton.core.featureflag.domain.entity.FeatureId
+
+enum class MailFeatureFlags(val featureId: FeatureId) {
+ ShowReviewAppDialog(FeatureId("RatingAndroidMail"))
+}
\ No newline at end of file
diff --git a/app/src/main/java/ch/protonmail/android/featureflags/RefreshFeatureFlags.kt b/app/src/main/java/ch/protonmail/android/featureflags/RefreshFeatureFlags.kt
new file mode 100644
index 000000000..5de04a291
--- /dev/null
+++ b/app/src/main/java/ch/protonmail/android/featureflags/RefreshFeatureFlags.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2022 Proton AG
+ *
+ * This file is part of 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 https://www.gnu.org/licenses/.
+ */
+
+package ch.protonmail.android.featureflags
+
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.launch
+import me.proton.core.accountmanager.domain.AccountManager
+import me.proton.core.domain.entity.UserId
+import me.proton.core.featureflag.domain.FeatureFlagManager
+import me.proton.core.featureflag.domain.entity.FeatureFlag
+import me.proton.core.util.kotlin.CoroutineScopeProvider
+import timber.log.Timber
+import javax.inject.Inject
+
+class RefreshFeatureFlags @Inject constructor(
+ private val scopeProvider: CoroutineScopeProvider,
+ private val featureFlagManager: FeatureFlagManager,
+ private val accountManager: AccountManager
+) {
+
+ private val scope get() = scopeProvider.GlobalIOSupervisedScope
+
+ fun refresh() {
+ scope.launch {
+ val accounts = accountManager.getAccounts().firstOrNull() ?: return@launch
+ Timber.d("Refreshing feature flags for ${accounts.count()} accounts")
+ accounts.map { it.userId }.forEach { userId ->
+ refreshCachedShowRatingsFeatureFlag(userId)
+ Timber.d("Rating feature flag refreshed for user $userId")
+ }
+ }
+ }
+
+ private suspend fun refreshCachedShowRatingsFeatureFlag(userId: UserId) {
+ featureFlagManager.getOrDefault(
+ userId = userId,
+ featureId = MailFeatureFlags.ShowReviewAppDialog.featureId,
+ default = FeatureFlag.default(MailFeatureFlags.ShowReviewAppDialog.featureId.id, false),
+ refresh = true
+ )
+ }
+
+}
diff --git a/app/src/main/java/ch/protonmail/android/mailbox/presentation/ui/MailboxActivity.kt b/app/src/main/java/ch/protonmail/android/mailbox/presentation/ui/MailboxActivity.kt
index 1c2885d7c..7a80f9bac 100644
--- a/app/src/main/java/ch/protonmail/android/mailbox/presentation/ui/MailboxActivity.kt
+++ b/app/src/main/java/ch/protonmail/android/mailbox/presentation/ui/MailboxActivity.kt
@@ -778,6 +778,8 @@ internal class MailboxActivity :
if (mailboxLocation == MessageLocationType.INBOX) {
userManager.currentUserId?.let { mailboxViewModel.clearNotifications(it) }
}
+
+ mailboxViewModel.startRateAppFlowIfNeeded()
}
override fun onPause() {
diff --git a/app/src/main/java/ch/protonmail/android/mailbox/presentation/viewmodel/MailboxViewModel.kt b/app/src/main/java/ch/protonmail/android/mailbox/presentation/viewmodel/MailboxViewModel.kt
index aa8cdafde..6c6304722 100644
--- a/app/src/main/java/ch/protonmail/android/mailbox/presentation/viewmodel/MailboxViewModel.kt
+++ b/app/src/main/java/ch/protonmail/android/mailbox/presentation/viewmodel/MailboxViewModel.kt
@@ -41,6 +41,7 @@ import ch.protonmail.android.domain.loadMoreMap
import ch.protonmail.android.drawer.presentation.mapper.DrawerFoldersAndLabelsSectionUiModelMapper
import ch.protonmail.android.drawer.presentation.model.DrawerFoldersAndLabelsSectionUiModel
import ch.protonmail.android.feature.NotLoggedIn
+import ch.protonmail.android.feature.rating.usecase.StartRateAppFlowIfNeeded
import ch.protonmail.android.labels.domain.LabelRepository
import ch.protonmail.android.labels.domain.model.Label
import ch.protonmail.android.labels.domain.model.LabelId
@@ -136,7 +137,8 @@ internal class MailboxViewModel @Inject constructor(
private val getMailSettings: GetMailSettings,
private val mailboxItemUiModelMapper: MailboxItemUiModelMapper,
private val fetchEventsAndReschedule: FetchEventsAndReschedule,
- private val clearNotificationsForUser: ClearNotificationsForUser
+ private val clearNotificationsForUser: ClearNotificationsForUser,
+ private val startRateAppFlowIfNeeded: StartRateAppFlowIfNeeded
) : ConnectivityBaseViewModel(verifyConnection, networkConfigurator) {
private val _manageLimitReachedWarning = MutableLiveData>()
@@ -722,6 +724,13 @@ internal class MailboxViewModel @Inject constructor(
getMailSettings(userId).map(::Right)
}
+ fun startRateAppFlowIfNeeded() {
+ val userId = userManager.currentUserId ?: return
+ viewModelScope.launch {
+ startRateAppFlowIfNeeded.invoke(userId)
+ }
+ }
+
data class GetMailboxItemsParameters(
val userId: UserId,
val labelId: LabelId,
diff --git a/app/src/test/java/ch/protonmail/android/feature/rating/MailboxScreenViewInMemoryRepositoryTest.kt b/app/src/test/java/ch/protonmail/android/feature/rating/MailboxScreenViewInMemoryRepositoryTest.kt
new file mode 100644
index 000000000..9e260fb43
--- /dev/null
+++ b/app/src/test/java/ch/protonmail/android/feature/rating/MailboxScreenViewInMemoryRepositoryTest.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2022 Proton AG
+ *
+ * This file is part of 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 https://www.gnu.org/licenses/.
+ */
+
+package ch.protonmail.android.feature.rating
+
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import kotlin.test.assertEquals
+
+class MailboxScreenViewInMemoryRepositoryTest {
+
+ private val showReviewAppRepository = MailboxScreenViewInMemoryRepository()
+
+ @Test
+ fun `increase mailbox screen views counter when record mailbox screen view is called`() = runTest {
+ // given
+ check(showReviewAppRepository.screenViewCount == 0)
+
+ // when
+ showReviewAppRepository.recordScreenView()
+
+ // then
+ assertEquals(1, showReviewAppRepository.screenViewCount)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/test/java/ch/protonmail/android/feature/rating/StartRateAppFlowTest.kt b/app/src/test/java/ch/protonmail/android/feature/rating/StartRateAppFlowTest.kt
new file mode 100644
index 000000000..3cd616ded
--- /dev/null
+++ b/app/src/test/java/ch/protonmail/android/feature/rating/StartRateAppFlowTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2022 Proton AG
+ *
+ * This file is part of 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 https://www.gnu.org/licenses/.
+ */
+
+package ch.protonmail.android.feature.rating
+
+import android.content.Context
+import com.google.android.play.core.review.ReviewManager
+import com.google.android.play.core.review.ReviewManagerFactory
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import io.mockk.unmockkStatic
+import io.mockk.verify
+import org.junit.After
+import org.junit.Test
+
+class StartRateAppFlowTest {
+
+ private val context: Context = mockk(relaxed = true)
+
+ private val startRateAppFlow = StartRateAppFlow(context)
+
+ @After
+ fun tearDown() {
+ unmockkStatic(ReviewManagerFactory::class)
+ }
+
+ @Test
+ fun `request review flow from google play review manager`() {
+ // given
+ val reviewMangerMock = mockk {
+ every { this@mockk.requestReviewFlow() } returns mockk(relaxed = true)
+ }
+ mockkStatic(ReviewManagerFactory::class)
+ every { ReviewManagerFactory.create(context) } returns reviewMangerMock
+
+ // when
+ startRateAppFlow()
+
+ // then
+ verify { reviewMangerMock.requestReviewFlow() }
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/ch/protonmail/android/feature/rating/usecase/StartRateAppFlowIfNeededTest.kt b/app/src/test/java/ch/protonmail/android/feature/rating/usecase/StartRateAppFlowIfNeededTest.kt
new file mode 100644
index 000000000..2c5c106f7
--- /dev/null
+++ b/app/src/test/java/ch/protonmail/android/feature/rating/usecase/StartRateAppFlowIfNeededTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2022 Proton AG
+ *
+ * This file is part of 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 https://www.gnu.org/licenses/.
+ */
+
+package ch.protonmail.android.feature.rating.usecase
+
+import ch.protonmail.android.feature.rating.MailboxScreenViewInMemoryRepository
+import ch.protonmail.android.feature.rating.StartRateAppFlow
+import ch.protonmail.android.featureflags.MailFeatureFlags
+import ch.protonmail.android.testdata.UserTestData
+import io.mockk.Called
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import kotlinx.coroutines.test.runTest
+import me.proton.core.domain.entity.UserId
+import me.proton.core.featureflag.domain.FeatureFlagManager
+import me.proton.core.featureflag.domain.entity.FeatureFlag
+import me.proton.core.featureflag.domain.entity.Scope
+import org.junit.Test
+
+class StartRateAppFlowIfNeededTest {
+
+ private val userId = UserTestData.userId
+
+ private val mailboxScreenViewsRepository: MailboxScreenViewInMemoryRepository = mockk()
+ private val featureFlagManager: FeatureFlagManager = mockk(relaxUnitFun = true)
+ private val startRateAppFlow: StartRateAppFlow = mockk(relaxUnitFun = true)
+
+ private val startRateAppFlowIfNeeded = StartRateAppFlowIfNeeded(
+ mailboxScreenViewsRepository,
+ featureFlagManager,
+ startRateAppFlow
+ )
+
+ @Test
+ fun `rate app flow is started when feature flag is true and mailbox screen views reached threshold`() = runTest {
+ // given
+ mockFeatureFlagValue(userId, true)
+ mockScreenViews(2)
+
+ // when
+ startRateAppFlowIfNeeded(userId)
+
+ // then
+ verify { startRateAppFlow() }
+ }
+
+ @Test
+ fun `rate app flow is not started when feature flag is false`() = runTest {
+ // given
+ mockFeatureFlagValue(userId, false)
+ mockScreenViews(2)
+
+ // when
+ startRateAppFlowIfNeeded(userId)
+
+ // then
+ verify { startRateAppFlow wasNot Called }
+ }
+
+ @Test
+ fun `rate app flow is not started when mailbox screen views are less than threshold`() = runTest {
+ // given
+ mockFeatureFlagValue(userId, true)
+ mockScreenViews(1)
+
+ // when
+ startRateAppFlowIfNeeded(userId)
+
+ // then
+ verify { startRateAppFlow wasNot Called }
+ }
+
+ @Test
+ fun `notify backend that the rate flow was started by disabling feature flag`() = runTest {
+ // given
+ mockFeatureFlagValue(userId, true)
+ mockScreenViews(2)
+
+ // when
+ startRateAppFlowIfNeeded(userId)
+
+ // then
+ val featureId = MailFeatureFlags.ShowReviewAppDialog.featureId
+ val featureFlag = FeatureFlag(userId, featureId, Scope.User, defaultValue = false, value = false)
+ coVerify { featureFlagManager.update(featureFlag) }
+ }
+
+ private suspend fun mockFeatureFlagValue(userId: UserId, isEnabled: Boolean) {
+ val featureId = MailFeatureFlags.ShowReviewAppDialog.featureId
+ coEvery {
+ featureFlagManager.getOrDefault(
+ userId = userId,
+ featureId = featureId,
+ default = FeatureFlag.default(featureId.id, false),
+ refresh = false
+ )
+ } returns FeatureFlag(userId, featureId, Scope.User, false, isEnabled)
+ }
+
+ private fun mockScreenViews(count: Int) {
+ every { mailboxScreenViewsRepository.screenViewCount } returns count
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/ch/protonmail/android/featureflags/RefreshFeatureFlagsTest.kt b/app/src/test/java/ch/protonmail/android/featureflags/RefreshFeatureFlagsTest.kt
new file mode 100644
index 000000000..745901951
--- /dev/null
+++ b/app/src/test/java/ch/protonmail/android/featureflags/RefreshFeatureFlagsTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2022 Proton AG
+ *
+ * This file is part of 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 https://www.gnu.org/licenses/.
+ */
+
+package ch.protonmail.android.featureflags
+
+import ch.protonmail.android.testdata.AccountTestData
+import ch.protonmail.android.testdata.UserTestData
+import io.mockk.Called
+import io.mockk.coEvery
+import io.mockk.coVerifyAll
+import io.mockk.mockk
+import io.mockk.verify
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import me.proton.core.accountmanager.domain.AccountManager
+import me.proton.core.domain.entity.UserId
+import me.proton.core.featureflag.domain.FeatureFlagManager
+import me.proton.core.featureflag.domain.entity.FeatureFlag
+import me.proton.core.featureflag.domain.entity.Scope
+import me.proton.core.util.kotlin.DefaultCoroutineScopeProvider
+import me.proton.core.util.kotlin.DefaultDispatcherProvider
+import org.junit.Test
+
+class RefreshFeatureFlagsTest {
+
+ private val featureFlagManager: FeatureFlagManager = mockk()
+
+ private val accountManager: AccountManager = mockk()
+
+ private val refreshFeatureFlags = RefreshFeatureFlags(
+ DefaultCoroutineScopeProvider(DefaultDispatcherProvider()),
+ featureFlagManager,
+ accountManager
+ )
+
+ @Test
+ fun `does nothing when there are no accounts`() = runTest {
+ // given
+ coEvery { accountManager.getAccounts() } returns flowOf()
+
+ // when
+ refreshFeatureFlags.refresh()
+
+ // then
+ verify { featureFlagManager wasNot Called }
+ }
+
+ @Test
+ fun `refresh show rating feature flag for each existing user`() = runTest {
+ // given
+ showRatingsFlagForUserMocked(UserTestData.userId, false)
+ showRatingsFlagForUserMocked(UserTestData.secondaryUserId, true)
+ coEvery { accountManager.getAccounts() } returns flowOf(AccountTestData.accounts)
+
+ // when
+ refreshFeatureFlags.refresh()
+
+ // then
+ coVerifyAll {
+ showRatingsFlagFetchedForUser(UserTestData.userId)
+ showRatingsFlagFetchedForUser(UserTestData.secondaryUserId)
+ }
+ }
+
+ private fun showRatingsFlagForUserMocked(userId: UserId, isEnabled: Boolean) {
+ coEvery {
+ featureFlagManager.getOrDefault(
+ userId = userId,
+ featureId = showReviewAppDialogFeatureFlag.featureId,
+ default = FeatureFlag.default(showReviewAppDialogFeatureFlag.featureId.id, false),
+ refresh = true
+ )
+ } returns FeatureFlag(userId, showReviewAppDialogFeatureFlag.featureId, Scope.User, false, isEnabled)
+ }
+
+ private suspend fun showRatingsFlagFetchedForUser(userId: UserId) {
+ featureFlagManager.getOrDefault(
+ userId = userId,
+ featureId = showReviewAppDialogFeatureFlag.featureId,
+ default = FeatureFlag.default(showReviewAppDialogFeatureFlag.featureId.id, false),
+ refresh = true
+ )
+ }
+
+ companion object {
+
+ private val showReviewAppDialogFeatureFlag = MailFeatureFlags.ShowReviewAppDialog
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/ch/protonmail/android/mailbox/presentation/viewmodel/MailboxViewModelTest.kt b/app/src/test/java/ch/protonmail/android/mailbox/presentation/viewmodel/MailboxViewModelTest.kt
index 6ae614c8e..59c6ed0f8 100644
--- a/app/src/test/java/ch/protonmail/android/mailbox/presentation/viewmodel/MailboxViewModelTest.kt
+++ b/app/src/test/java/ch/protonmail/android/mailbox/presentation/viewmodel/MailboxViewModelTest.kt
@@ -41,6 +41,7 @@ import ch.protonmail.android.di.JobEntryPoint
import ch.protonmail.android.domain.loadMoreFlowOf
import ch.protonmail.android.domain.withLoadMore
import ch.protonmail.android.feature.NotLoggedIn
+import ch.protonmail.android.feature.rating.usecase.StartRateAppFlowIfNeeded
import ch.protonmail.android.labels.domain.LabelRepository
import ch.protonmail.android.labels.domain.model.Label
import ch.protonmail.android.labels.domain.model.LabelId
@@ -73,6 +74,7 @@ import ch.protonmail.android.usecase.delete.EmptyFolder
import ch.protonmail.android.usecase.message.ChangeMessagesReadStatus
import ch.protonmail.android.usecase.message.ChangeMessagesStarredStatus
import dagger.hilt.EntryPoints
+import io.mockk.Called
import io.mockk.called
import io.mockk.coEvery
import io.mockk.coVerify
@@ -82,6 +84,7 @@ import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.unmockkStatic
+import io.mockk.verify
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
@@ -178,6 +181,7 @@ class MailboxViewModelTest : ArchTest by ArchTest(),
private val fetchEventsAndReschedule: FetchEventsAndReschedule = mockk {
coEvery { this@mockk.invoke() } just runs
}
+ private val startRateAppFlowIfNeeded: StartRateAppFlowIfNeeded = mockk()
private lateinit var viewModel: MailboxViewModel
@@ -272,7 +276,8 @@ class MailboxViewModelTest : ArchTest by ArchTest(),
getMailSettings = getMailSettings,
mailboxItemUiModelMapper = mailboxItemUiModelMapper,
fetchEventsAndReschedule = fetchEventsAndReschedule,
- clearNotificationsForUser = clearNotificationsForUser
+ clearNotificationsForUser = clearNotificationsForUser,
+ startRateAppFlowIfNeeded = startRateAppFlowIfNeeded
)
}
@@ -606,6 +611,30 @@ class MailboxViewModelTest : ArchTest by ArchTest(),
}
}
+ @Test
+ fun `calls to start rate app flow if needed delegates to use case`() = runTest {
+ // given
+ coEvery { startRateAppFlowIfNeeded.invoke(testUserId) } returns Unit
+
+ // when
+ viewModel.startRateAppFlowIfNeeded()
+
+ // then
+ coVerify { startRateAppFlowIfNeeded.invoke(testUserId) }
+ }
+
+ @Test
+ fun `start rate app flow if needed is not called when current user id is invalid`() = runTest {
+ // given
+ every { userManager.currentUserId } returns null
+
+ // when
+ viewModel.startRateAppFlowIfNeeded()
+
+ // then
+ verify { startRateAppFlowIfNeeded wasNot Called }
+ }
+
private fun List.toMailboxState(): MailboxListState.Data =
MailboxListState.Data(this, isFreshData = false, shouldResetPosition = true)
diff --git a/app/src/test/java/ch/protonmail/android/testdata/AccountTestData.kt b/app/src/test/java/ch/protonmail/android/testdata/AccountTestData.kt
new file mode 100644
index 000000000..2210d62c5
--- /dev/null
+++ b/app/src/test/java/ch/protonmail/android/testdata/AccountTestData.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.testdata
+
+import me.proton.core.account.domain.entity.Account
+import me.proton.core.account.domain.entity.AccountDetails
+import me.proton.core.account.domain.entity.AccountState.Ready
+import me.proton.core.account.domain.entity.AccountType.Internal
+import me.proton.core.account.domain.entity.SessionDetails
+import me.proton.core.account.domain.entity.SessionState.Authenticated
+import me.proton.core.network.domain.session.SessionId
+
+object AccountTestData {
+ private const val RAW_USERNAME = "username"
+ private const val RAW_EMAIL = "email@protonmail.ch"
+ private const val INITIAL_EVENT_ID = "event_id"
+
+ val primaryAccount = Account(
+ userId = UserTestData.userId,
+ username = RAW_USERNAME,
+ email = RAW_EMAIL,
+ state = Ready,
+ sessionId = SessionId(UserTestData.userId.id),
+ sessionState = Authenticated,
+ details = AccountDetails(
+ null,
+ SessionDetails(
+ initialEventId = INITIAL_EVENT_ID,
+ requiredAccountType = Internal,
+ secondFactorEnabled = true,
+ twoPassModeEnabled = true,
+ password = null
+ )
+ )
+ )
+
+ val secondaryAccount = Account(
+ userId = UserTestData.secondaryUserId,
+ username = RAW_USERNAME,
+ email = RAW_EMAIL,
+ state = Ready,
+ sessionId = SessionId(UserTestData.secondaryUserId.id),
+ sessionState = Authenticated,
+ details = AccountDetails(
+ null,
+ SessionDetails(
+ initialEventId = INITIAL_EVENT_ID,
+ requiredAccountType = Internal,
+ secondFactorEnabled = true,
+ twoPassModeEnabled = true,
+ password = null
+ )
+ )
+ )
+
+ val accounts = listOf(primaryAccount, secondaryAccount)
+
+}
diff --git a/app/src/test/java/ch/protonmail/android/testdata/UserTestData.kt b/app/src/test/java/ch/protonmail/android/testdata/UserTestData.kt
index 782d2352f..3ef058f08 100644
--- a/app/src/test/java/ch/protonmail/android/testdata/UserTestData.kt
+++ b/app/src/test/java/ch/protonmail/android/testdata/UserTestData.kt
@@ -28,7 +28,9 @@ import me.proton.core.domain.entity.UserId
object UserTestData {
private const val RAW_ID = "user_id"
+ private const val RAW_SECONDARY_USER_ID = "secondary_user_id"
val userId = UserId(RAW_ID)
+ val secondaryUserId = UserId(RAW_SECONDARY_USER_ID)
fun withAddresses(addressesList: Addresses): User = mockk {
every { addresses } returns addressesList
diff --git a/buildSrc/src/main/kotlin/libraries.kt b/buildSrc/src/main/kotlin/libraries.kt
index 86cc7d88a..23d8f1683 100644
--- a/buildSrc/src/main/kotlin/libraries.kt
+++ b/buildSrc/src/main/kotlin/libraries.kt
@@ -82,6 +82,7 @@ val DependencyHandler.`android-startup-runtime` get() = androidx("startup",
val DependencyHandler.`lifecycle-extensions` get() = androidxLifecycle("extensions") version `lifecycle-extensions version`
val DependencyHandler.`room-rxJava` get() = androidxRoom("rxjava2")
val DependencyHandler.`safetyNet` get() = playServices("safetynet")
+val DependencyHandler.`google-play-review` get() = google("android.play", "review") version `google-play-core-libs`
fun DependencyHandler.googleServices(moduleSuffix: String? = null, version: String = `googleServices version`) =
google("gms", "google-services", moduleSuffix, version)
diff --git a/buildSrc/src/main/kotlin/versionsConfig.kt b/buildSrc/src/main/kotlin/versionsConfig.kt
index fb19ae8f7..ee5ba58fd 100644
--- a/buildSrc/src/main/kotlin/versionsConfig.kt
+++ b/buildSrc/src/main/kotlin/versionsConfig.kt
@@ -62,7 +62,7 @@ fun initVersions() {
}
// Proton Core
-const val `Proton-core version` = "10.1.0"
+const val `Proton-core version` = "10.2.0"
// Test
const val `aerogear version` = "1.0.0" // Released: Mar 23, 2013
@@ -90,6 +90,7 @@ const val `flexbox version` = "2.0.1" // Released: Jan
const val `lifecycle-extensions version` = "2.2.0" // Released: Jan 22, 2020
const val `googleServices version` = "4.3.3" // Released: Nov 11, 2019
const val `playServices version` = "17.0.0" // Released: Jun 19, 2019
+const val `google-play-core-libs` = "2.0.1"
// Other
const val `apache-commons-lang version` = "3.4" // Released: Apr 03, 2015