Merge branch 'feat/3071_prompt-whitelisted-user-to-rate-app' into 'develop'
Require users to rate app when meeting conditions See merge request android/mail/proton-mail-android!1251
This commit is contained in:
commit
d9aab635d8
|
@ -13,6 +13,7 @@
|
|||
<option name="configurationPath" value="" />
|
||||
<option name="exclusions">
|
||||
<set>
|
||||
<option value="kotlin/libraries.kt" />
|
||||
<option value="versionsConfig.kt" />
|
||||
</set>
|
||||
</option>
|
||||
|
|
|
@ -357,6 +357,7 @@ dependencies {
|
|||
`constraint-layout`,
|
||||
`material`,
|
||||
`paging-runtime`,
|
||||
`google-play-review`,
|
||||
|
||||
// Lifecycle
|
||||
`lifecycle-extensions`,
|
||||
|
|
|
@ -144,6 +144,11 @@
|
|||
android:name="ch.protonmail.android.onboarding.base.presentation.StartOnboardingObserverInitializer"
|
||||
android:value="androidx.startup"
|
||||
tools:node="remove" />
|
||||
|
||||
<meta-data
|
||||
android:name="ch.protonmail.android.featureflags.FeatureFlagsInitializer"
|
||||
android:value="androidx.startup"
|
||||
tools:node="remove" />
|
||||
</provider>
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
|
|
|
@ -87,6 +87,7 @@ import ch.protonmail.android.exceptions.ErrorStateGeneratorsKt;
|
|||
import ch.protonmail.android.feature.account.AccountManagerKt;
|
||||
import ch.protonmail.android.feature.account.AccountStateHandlerInitializer;
|
||||
import ch.protonmail.android.feature.account.CoreAccountManagerMigration;
|
||||
import ch.protonmail.android.featureflags.FeatureFlagsInitializer;
|
||||
import ch.protonmail.android.notifications.data.remote.fcm.MultiUserFcmTokenManager;
|
||||
import ch.protonmail.android.notifications.presentation.utils.NotificationServer;
|
||||
import ch.protonmail.android.onboarding.base.presentation.AddStartOnboardingObserverIfNeeded;
|
||||
|
@ -109,8 +110,8 @@ import me.proton.core.auth.presentation.MissingScopeInitializer;
|
|||
import me.proton.core.crypto.validator.presentation.init.CryptoValidatorInitializer;
|
||||
import me.proton.core.domain.entity.UserId;
|
||||
import me.proton.core.humanverification.presentation.HumanVerificationInitializer;
|
||||
import me.proton.core.plan.presentation.UnredeemedPurchaseInitializer;
|
||||
import me.proton.core.network.presentation.init.UnAuthSessionFetcherInitializer;
|
||||
import me.proton.core.plan.presentation.UnredeemedPurchaseInitializer;
|
||||
import me.proton.core.util.kotlin.CoreLogger;
|
||||
import studio.forface.viewstatestore.ViewStateStoreConfig;
|
||||
import timber.log.Timber;
|
||||
|
@ -234,6 +235,7 @@ public class ProtonMailApplication extends Application implements androidx.work.
|
|||
appInitializer.initializeComponent(MissingScopeInitializer.class);
|
||||
appInitializer.initializeComponent(UnredeemedPurchaseInitializer.class);
|
||||
appInitializer.initializeComponent(UnAuthSessionFetcherInitializer.class);
|
||||
appInitializer.initializeComponent(FeatureFlagsInitializer.class);
|
||||
|
||||
checkForUpdateAndClearCache();
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ import ch.protonmail.android.events.DownloadEmbeddedImagesEvent
|
|||
import ch.protonmail.android.events.DownloadedAttachmentEvent
|
||||
import ch.protonmail.android.events.PostPhishingReportEvent
|
||||
import ch.protonmail.android.events.Status
|
||||
import ch.protonmail.android.feature.rating.MailboxScreenViewInMemoryRepository
|
||||
import ch.protonmail.android.jobs.PostSpamJob
|
||||
import ch.protonmail.android.labels.domain.model.LabelId
|
||||
import ch.protonmail.android.labels.domain.model.LabelType
|
||||
|
@ -122,6 +123,9 @@ internal class MessageDetailsActivity : BaseStoragePermissionActivity() {
|
|||
@Inject
|
||||
lateinit var accountSettingsRepository: AccountSettingsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var mailboxScreenViewRepository: MailboxScreenViewInMemoryRepository
|
||||
|
||||
private lateinit var messageOrConversationId: String
|
||||
private lateinit var messageExpandableAdapter: MessageDetailsAdapter
|
||||
private lateinit var primaryBaseActivity: Context
|
||||
|
@ -358,6 +362,11 @@ internal class MessageDetailsActivity : BaseStoragePermissionActivity() {
|
|||
mApp.bus.unregister(this)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
mailboxScreenViewRepository.recordScreenView()
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
|
@ -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}")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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<RefreshFeatureFlags> {
|
||||
|
||||
override fun create(context: Context): RefreshFeatureFlags {
|
||||
val refreshFeatureFlags = EntryPointAccessors.fromApplication(
|
||||
context.applicationContext,
|
||||
FeatureFlagsEntryPoint::class.java
|
||||
).refreshFeatureFlags()
|
||||
refreshFeatureFlags.refresh()
|
||||
return refreshFeatureFlags
|
||||
}
|
||||
|
||||
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface FeatureFlagsEntryPoint {
|
||||
|
||||
fun refreshFeatureFlags(): RefreshFeatureFlags
|
||||
}
|
||||
}
|
|
@ -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"))
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -778,6 +778,8 @@ internal class MailboxActivity :
|
|||
if (mailboxLocation == MessageLocationType.INBOX) {
|
||||
userManager.currentUserId?.let { mailboxViewModel.clearNotifications(it) }
|
||||
}
|
||||
|
||||
mailboxViewModel.startRateAppFlowIfNeeded()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
|
|
@ -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<Event<Boolean>>()
|
||||
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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<ReviewManager> {
|
||||
every { this@mockk.requestReviewFlow() } returns mockk(relaxed = true)
|
||||
}
|
||||
mockkStatic(ReviewManagerFactory::class)
|
||||
every { ReviewManagerFactory.create(context) } returns reviewMangerMock
|
||||
|
||||
// when
|
||||
startRateAppFlow()
|
||||
|
||||
// then
|
||||
verify { reviewMangerMock.requestReviewFlow() }
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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<MailboxItemUiModel>.toMailboxState(): MailboxListState.Data =
|
||||
MailboxListState.Data(this, isFreshData = false, shouldResetPosition = true)
|
||||
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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)
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue