Updated on 2026-08-14
This commit is contained in:
parent
4e4e360ae1
commit
9adbde6e55
12 changed files with 454 additions and 75 deletions
|
|
@ -1,9 +1,12 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.hotwallet.CheckHotWalletUpgradeBannerUseCase
|
||||
import com.tangem.domain.hotwallet.CloseHotWalletUpgradeBannerUseCase
|
||||
import com.tangem.domain.hotwallet.GetAccessCodeSkippedUseCase
|
||||
import com.tangem.domain.hotwallet.IsHotWalletCreationSupported
|
||||
import com.tangem.domain.hotwallet.IsAccessCodeSimpleUseCase
|
||||
import com.tangem.domain.hotwallet.SetAccessCodeSkippedUseCase
|
||||
import com.tangem.domain.hotwallet.ShouldShowUpgradeHotWalletBannerUseCase
|
||||
import com.tangem.domain.hotwallet.repository.HotWalletRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -40,4 +43,28 @@ internal object HotWalletDomainModule {
|
|||
): IsHotWalletCreationSupported {
|
||||
return IsHotWalletCreationSupported(hotWalletRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCheckHotWalletUpgradeBannerUseCase(
|
||||
hotWalletRepository: HotWalletRepository,
|
||||
): CheckHotWalletUpgradeBannerUseCase {
|
||||
return CheckHotWalletUpgradeBannerUseCase(hotWalletRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCloseHotWalletUpgradeBannerUseCase(
|
||||
hotWalletRepository: HotWalletRepository,
|
||||
): CloseHotWalletUpgradeBannerUseCase {
|
||||
return CloseHotWalletUpgradeBannerUseCase(hotWalletRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideShouldShowUpgradeHotWalletBannerUseCase(
|
||||
hotWalletRepository: HotWalletRepository,
|
||||
): ShouldShowUpgradeHotWalletBannerUseCase {
|
||||
return ShouldShowUpgradeHotWalletBannerUseCase(hotWalletRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -134,10 +134,6 @@ object PreferencesKeys {
|
|||
|
||||
val SHOULD_SHOW_UPGRADE_BANNER_KEY by lazy { stringPreferencesKey(name = "shouldShowUpgradeBanner") }
|
||||
|
||||
val SHOULD_SHOW_NEXT_TIME_UPGRADE_BANNER_KEY by lazy {
|
||||
stringPreferencesKey(name = "shouldShowNextTimeUpgradeBanner")
|
||||
}
|
||||
|
||||
val UPGRADE_BANNER_CLOSURE_TIMESTAMP_KEY by lazy { stringPreferencesKey(name = "upgradeBannerClosureTimestamp") }
|
||||
|
||||
val WALLET_CREATION_TIMESTAMP_KEY by lazy { stringPreferencesKey(name = "walletCreationTimestamp") }
|
||||
|
|
|
|||
|
|
@ -10,11 +10,14 @@ import com.tangem.domain.hotwallet.repository.HotWalletRepository
|
|||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
internal class DefaultHotWalletRepository(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : HotWalletRepository {
|
||||
|
||||
private val firstTopUpDetectedThisSession = ConcurrentHashMap<String, Unit>()
|
||||
|
||||
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.Q)
|
||||
override fun isWalletCreationSupported(): Boolean {
|
||||
return BuildConfig.DEBUG || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||
|
|
@ -50,28 +53,12 @@ internal class DefaultHotWalletRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override fun shouldShowNextTimeUpgradeBanner(userWalletId: UserWalletId): Flow<Boolean> = appPreferencesStore
|
||||
.getObjectMap<Boolean>(PreferencesKeys.SHOULD_SHOW_NEXT_TIME_UPGRADE_BANNER_KEY)
|
||||
.map { it[userWalletId.stringValue] == true }
|
||||
|
||||
override suspend fun setShouldShowNextTimeUpgradeBanner(userWalletId: UserWalletId, shouldShow: Boolean) {
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
mutablePreferences.setObjectMap(
|
||||
key = PreferencesKeys.SHOULD_SHOW_NEXT_TIME_UPGRADE_BANNER_KEY,
|
||||
value = mutablePreferences.getObjectMap<Boolean>(
|
||||
PreferencesKeys.SHOULD_SHOW_NEXT_TIME_UPGRADE_BANNER_KEY,
|
||||
)
|
||||
.plus(userWalletId.stringValue to shouldShow),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getUpgradeBannerClosureTimestamp(userWalletId: UserWalletId): Long? {
|
||||
return appPreferencesStore
|
||||
.getObjectMapSync<Long>(PreferencesKeys.UPGRADE_BANNER_CLOSURE_TIMESTAMP_KEY)[userWalletId.stringValue]
|
||||
}
|
||||
|
||||
override suspend fun setUpgradeBannerClosureTimestamp(userWalletId: UserWalletId, timestamp: Long) {
|
||||
override suspend fun setUpgradeBannerClosureTimestamp(userWalletId: UserWalletId, timestamp: Long?) {
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
mutablePreferences.setObjectMap(
|
||||
key = PreferencesKeys.UPGRADE_BANNER_CLOSURE_TIMESTAMP_KEY,
|
||||
|
|
@ -110,4 +97,12 @@ internal class DefaultHotWalletRepository(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isFirstTopUpDetectedThisSession(userWalletId: UserWalletId): Boolean {
|
||||
return firstTopUpDetectedThisSession.containsKey(userWalletId.stringValue)
|
||||
}
|
||||
|
||||
override fun markFirstTopUpDetectedThisSession(userWalletId: UserWalletId) {
|
||||
firstTopUpDetectedThisSession[userWalletId.stringValue] = Unit
|
||||
}
|
||||
}
|
||||
|
|
@ -14,4 +14,10 @@ dependencies {
|
|||
implementation(projects.domain.wallets.models)
|
||||
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.arrow.core)
|
||||
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(deps.test.mockk)
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.tangem.domain.hotwallet
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.hotwallet.repository.HotWalletRepository
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class CheckHotWalletUpgradeBannerUseCase(
|
||||
private val hotWalletRepository: HotWalletRepository,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
walletId: UserWalletId,
|
||||
hasBalance: Boolean,
|
||||
shouldShowUpgradeBanner: Boolean,
|
||||
): Either<Throwable, Boolean> = try {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
val creationTimestamp = hotWalletRepository.getWalletCreationTimestamp(walletId)
|
||||
|
||||
val creationTimestampActual = if (creationTimestamp == null) {
|
||||
// If creationTimestamp is null (wallet was created before this feature was released),
|
||||
// store the current timestamp and use it below
|
||||
hotWalletRepository.setWalletCreationTimestamp(walletId, currentTime)
|
||||
currentTime
|
||||
} else {
|
||||
creationTimestamp
|
||||
}
|
||||
|
||||
val closureTimestamp = hotWalletRepository.getUpgradeBannerClosureTimestamp(walletId)
|
||||
val hasHadFirstTopUp = hotWalletRepository.hasHadFirstTopUp(walletId)
|
||||
|
||||
val daysSinceCreation = TimeUnit.MILLISECONDS.toDays(currentTime - creationTimestampActual)
|
||||
val daysSinceClosure = closureTimestamp?.let { TimeUnit.MILLISECONDS.toDays(currentTime - it) }
|
||||
|
||||
// Wallet balance is positive, but the first top-up hasn't been tracked yet
|
||||
if (hasBalance && !hasHadFirstTopUp) {
|
||||
hotWalletRepository.setHasHadFirstTopUp(walletId, true)
|
||||
hotWalletRepository.setShouldShowUpgradeBanner(walletId, true)
|
||||
hotWalletRepository.setUpgradeBannerClosureTimestamp(walletId, null)
|
||||
hotWalletRepository.markFirstTopUpDetectedThisSession(walletId)
|
||||
}
|
||||
|
||||
val shouldShow = when {
|
||||
// Banner should be shown (e.g., because of the first top-up in the previous session)
|
||||
shouldShowUpgradeBanner -> !hotWalletRepository.isFirstTopUpDetectedThisSession(walletId)
|
||||
// Banner was closed; it happened more than BANNER_RESHOW_DAYS (30) days ago
|
||||
closureTimestamp != null && daysSinceClosure != null && daysSinceClosure >= BANNER_RESHOW_DAYS -> true
|
||||
// Banner hasn't been closed; wallet was created more than BANNER_RESHOW_DAYS (30) days ago
|
||||
closureTimestamp == null && daysSinceCreation >= BANNER_RESHOW_DAYS -> true
|
||||
else -> false
|
||||
}
|
||||
shouldShow.right()
|
||||
} catch (e: Exception) {
|
||||
e.left()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val BANNER_RESHOW_DAYS = 30L
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.domain.hotwallet
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.hotwallet.repository.HotWalletRepository
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
class CloseHotWalletUpgradeBannerUseCase(
|
||||
private val hotWalletRepository: HotWalletRepository,
|
||||
) {
|
||||
suspend operator fun invoke(walletId: UserWalletId): Either<Throwable, Unit> = try {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
hotWalletRepository.setShouldShowUpgradeBanner(walletId, false)
|
||||
hotWalletRepository.setUpgradeBannerClosureTimestamp(walletId, currentTime)
|
||||
Unit.right()
|
||||
} catch (e: Exception) {
|
||||
e.left()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.domain.hotwallet
|
||||
|
||||
import com.tangem.domain.hotwallet.repository.HotWalletRepository
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class ShouldShowUpgradeHotWalletBannerUseCase(
|
||||
private val hotWalletRepository: HotWalletRepository,
|
||||
) {
|
||||
operator fun invoke(userWalletId: UserWalletId): Flow<Boolean> =
|
||||
hotWalletRepository.shouldShowUpgradeBanner(userWalletId)
|
||||
}
|
||||
|
|
@ -17,13 +17,9 @@ interface HotWalletRepository {
|
|||
|
||||
suspend fun setShouldShowUpgradeBanner(userWalletId: UserWalletId, shouldShow: Boolean)
|
||||
|
||||
fun shouldShowNextTimeUpgradeBanner(userWalletId: UserWalletId): Flow<Boolean>
|
||||
|
||||
suspend fun setShouldShowNextTimeUpgradeBanner(userWalletId: UserWalletId, shouldShow: Boolean)
|
||||
|
||||
suspend fun getUpgradeBannerClosureTimestamp(userWalletId: UserWalletId): Long?
|
||||
|
||||
suspend fun setUpgradeBannerClosureTimestamp(userWalletId: UserWalletId, timestamp: Long)
|
||||
suspend fun setUpgradeBannerClosureTimestamp(userWalletId: UserWalletId, timestamp: Long?)
|
||||
|
||||
suspend fun getWalletCreationTimestamp(userWalletId: UserWalletId): Long?
|
||||
|
||||
|
|
@ -32,4 +28,8 @@ interface HotWalletRepository {
|
|||
suspend fun hasHadFirstTopUp(userWalletId: UserWalletId): Boolean
|
||||
|
||||
suspend fun setHasHadFirstTopUp(userWalletId: UserWalletId, hasTopUp: Boolean)
|
||||
|
||||
fun isFirstTopUpDetectedThisSession(userWalletId: UserWalletId): Boolean
|
||||
|
||||
fun markFirstTopUpDetectedThisSession(userWalletId: UserWalletId)
|
||||
}
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
package com.tangem.domain.hotwallet
|
||||
|
||||
import arrow.core.Either
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.hotwallet.repository.HotWalletRepository
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
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 org.junit.Test
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class CheckHotWalletUpgradeBannerUseCaseTest {
|
||||
|
||||
private val hotWalletRepository: HotWalletRepository = mockk(relaxed = true)
|
||||
private val useCase = CheckHotWalletUpgradeBannerUseCase(hotWalletRepository)
|
||||
|
||||
private val walletId = UserWalletId("0123456789ABCDEF")
|
||||
|
||||
@Test
|
||||
fun `GIVEN creation timestamp is null WHEN invoke THEN set timestamp and return false`() = runTest {
|
||||
coEvery { hotWalletRepository.getWalletCreationTimestamp(walletId) } returns null
|
||||
coEvery { hotWalletRepository.getUpgradeBannerClosureTimestamp(walletId) } returns null
|
||||
coEvery { hotWalletRepository.hasHadFirstTopUp(walletId) } returns false
|
||||
every { hotWalletRepository.isFirstTopUpDetectedThisSession(walletId) } returns false
|
||||
|
||||
val result = useCase(
|
||||
walletId = walletId,
|
||||
hasBalance = false,
|
||||
shouldShowUpgradeBanner = false,
|
||||
)
|
||||
|
||||
assertThat(result).isInstanceOf(Either.Right::class.java)
|
||||
assertThat((result as Either.Right).value).isFalse()
|
||||
coVerify { hotWalletRepository.setWalletCreationTimestamp(walletId, any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN shouldShowUpgradeBanner is true and hasBalance WHEN invoke THEN return true`() = runTest {
|
||||
val creationTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(5)
|
||||
coEvery { hotWalletRepository.getWalletCreationTimestamp(walletId) } returns creationTimestamp
|
||||
coEvery { hotWalletRepository.getUpgradeBannerClosureTimestamp(walletId) } returns null
|
||||
coEvery { hotWalletRepository.hasHadFirstTopUp(walletId) } returns true
|
||||
every { hotWalletRepository.isFirstTopUpDetectedThisSession(walletId) } returns false
|
||||
|
||||
val result = useCase(
|
||||
walletId = walletId,
|
||||
hasBalance = true,
|
||||
shouldShowUpgradeBanner = true,
|
||||
)
|
||||
|
||||
assertThat(result).isInstanceOf(Either.Right::class.java)
|
||||
assertThat((result as Either.Right).value).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN shouldShowUpgradeBanner is true WHEN invoke THEN return true regardless of balance`() = runTest {
|
||||
val creationTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(5)
|
||||
coEvery { hotWalletRepository.getWalletCreationTimestamp(walletId) } returns creationTimestamp
|
||||
coEvery { hotWalletRepository.getUpgradeBannerClosureTimestamp(walletId) } returns null
|
||||
coEvery { hotWalletRepository.hasHadFirstTopUp(walletId) } returns true
|
||||
every { hotWalletRepository.isFirstTopUpDetectedThisSession(walletId) } returns false
|
||||
|
||||
val result = useCase(
|
||||
walletId = walletId,
|
||||
hasBalance = false,
|
||||
shouldShowUpgradeBanner = true,
|
||||
)
|
||||
|
||||
assertThat(result).isInstanceOf(Either.Right::class.java)
|
||||
assertThat((result as Either.Right).value).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN closure timestamp exists and 30 days since closure WHEN invoke THEN return true`() = runTest {
|
||||
val creationTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60)
|
||||
val closureTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31)
|
||||
coEvery { hotWalletRepository.getWalletCreationTimestamp(walletId) } returns creationTimestamp
|
||||
coEvery { hotWalletRepository.getUpgradeBannerClosureTimestamp(walletId) } returns closureTimestamp
|
||||
coEvery { hotWalletRepository.hasHadFirstTopUp(walletId) } returns true
|
||||
every { hotWalletRepository.isFirstTopUpDetectedThisSession(walletId) } returns false
|
||||
|
||||
val result = useCase(
|
||||
walletId = walletId,
|
||||
hasBalance = true,
|
||||
shouldShowUpgradeBanner = false,
|
||||
)
|
||||
|
||||
assertThat(result).isInstanceOf(Either.Right::class.java)
|
||||
assertThat((result as Either.Right).value).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN closure timestamp exists but less than 30 days since closure WHEN invoke THEN return false`() =
|
||||
runTest {
|
||||
val creationTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60)
|
||||
val closureTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(15)
|
||||
coEvery { hotWalletRepository.getWalletCreationTimestamp(walletId) } returns creationTimestamp
|
||||
coEvery { hotWalletRepository.getUpgradeBannerClosureTimestamp(walletId) } returns closureTimestamp
|
||||
coEvery { hotWalletRepository.hasHadFirstTopUp(walletId) } returns true
|
||||
every { hotWalletRepository.isFirstTopUpDetectedThisSession(walletId) } returns false
|
||||
|
||||
val result = useCase(
|
||||
walletId = walletId,
|
||||
hasBalance = true,
|
||||
shouldShowUpgradeBanner = false,
|
||||
)
|
||||
|
||||
assertThat(result).isInstanceOf(Either.Right::class.java)
|
||||
assertThat((result as Either.Right).value).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no flags set and no closure and 30 days since creation WHEN invoke THEN return true`() = runTest {
|
||||
val creationTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31)
|
||||
coEvery { hotWalletRepository.getWalletCreationTimestamp(walletId) } returns creationTimestamp
|
||||
coEvery { hotWalletRepository.getUpgradeBannerClosureTimestamp(walletId) } returns null
|
||||
coEvery { hotWalletRepository.hasHadFirstTopUp(walletId) } returns false
|
||||
every { hotWalletRepository.isFirstTopUpDetectedThisSession(walletId) } returns false
|
||||
|
||||
val result = useCase(
|
||||
walletId = walletId,
|
||||
hasBalance = false,
|
||||
shouldShowUpgradeBanner = false,
|
||||
)
|
||||
|
||||
assertThat(result).isInstanceOf(Either.Right::class.java)
|
||||
assertThat((result as Either.Right).value).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no flags set but less than 30 days since creation WHEN invoke THEN return false`() = runTest {
|
||||
val creationTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(15)
|
||||
coEvery { hotWalletRepository.getWalletCreationTimestamp(walletId) } returns creationTimestamp
|
||||
coEvery { hotWalletRepository.getUpgradeBannerClosureTimestamp(walletId) } returns null
|
||||
coEvery { hotWalletRepository.hasHadFirstTopUp(walletId) } returns false
|
||||
every { hotWalletRepository.isFirstTopUpDetectedThisSession(walletId) } returns false
|
||||
|
||||
val result = useCase(
|
||||
walletId = walletId,
|
||||
hasBalance = false,
|
||||
shouldShowUpgradeBanner = false,
|
||||
)
|
||||
|
||||
assertThat(result).isInstanceOf(Either.Right::class.java)
|
||||
assertThat((result as Either.Right).value).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no flags set but closure timestamp exists WHEN invoke THEN return false`() = runTest {
|
||||
val creationTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60)
|
||||
val closureTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(5)
|
||||
coEvery { hotWalletRepository.getWalletCreationTimestamp(walletId) } returns creationTimestamp
|
||||
coEvery { hotWalletRepository.getUpgradeBannerClosureTimestamp(walletId) } returns closureTimestamp
|
||||
coEvery { hotWalletRepository.hasHadFirstTopUp(walletId) } returns false
|
||||
every { hotWalletRepository.isFirstTopUpDetectedThisSession(walletId) } returns false
|
||||
|
||||
val result = useCase(
|
||||
walletId = walletId,
|
||||
hasBalance = false,
|
||||
shouldShowUpgradeBanner = false,
|
||||
)
|
||||
|
||||
assertThat(result).isInstanceOf(Either.Right::class.java)
|
||||
assertThat((result as Either.Right).value).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN first top-up detected WHEN invoke THEN return false and mark session`() = runTest {
|
||||
val creationTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(5)
|
||||
coEvery { hotWalletRepository.getWalletCreationTimestamp(walletId) } returns creationTimestamp
|
||||
coEvery { hotWalletRepository.getUpgradeBannerClosureTimestamp(walletId) } returns null
|
||||
coEvery { hotWalletRepository.hasHadFirstTopUp(walletId) } returns false
|
||||
every { hotWalletRepository.isFirstTopUpDetectedThisSession(walletId) } returns false
|
||||
|
||||
val result = useCase(
|
||||
walletId = walletId,
|
||||
hasBalance = true,
|
||||
shouldShowUpgradeBanner = false,
|
||||
)
|
||||
|
||||
assertThat(result).isInstanceOf(Either.Right::class.java)
|
||||
assertThat((result as Either.Right).value).isFalse()
|
||||
coVerify { hotWalletRepository.setHasHadFirstTopUp(walletId, true) }
|
||||
coVerify { hotWalletRepository.setShouldShowUpgradeBanner(walletId, true) }
|
||||
coVerify { hotWalletRepository.setUpgradeBannerClosureTimestamp(walletId, null) }
|
||||
verify { hotWalletRepository.markFirstTopUpDetectedThisSession(walletId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN first top-up detected this session WHEN invoke THEN return false`() = runTest {
|
||||
val creationTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(5)
|
||||
coEvery { hotWalletRepository.getWalletCreationTimestamp(walletId) } returns creationTimestamp
|
||||
coEvery { hotWalletRepository.getUpgradeBannerClosureTimestamp(walletId) } returns null
|
||||
coEvery { hotWalletRepository.hasHadFirstTopUp(walletId) } returns true
|
||||
every { hotWalletRepository.isFirstTopUpDetectedThisSession(walletId) } returns true
|
||||
|
||||
val result = useCase(
|
||||
walletId = walletId,
|
||||
hasBalance = true,
|
||||
shouldShowUpgradeBanner = true,
|
||||
)
|
||||
|
||||
assertThat(result).isInstanceOf(Either.Right::class.java)
|
||||
assertThat((result as Either.Right).value).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN already had first top-up WHEN invoke with balance THEN do not set flags again`() = runTest {
|
||||
val creationTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(5)
|
||||
coEvery { hotWalletRepository.getWalletCreationTimestamp(walletId) } returns creationTimestamp
|
||||
coEvery { hotWalletRepository.getUpgradeBannerClosureTimestamp(walletId) } returns null
|
||||
coEvery { hotWalletRepository.hasHadFirstTopUp(walletId) } returns true
|
||||
every { hotWalletRepository.isFirstTopUpDetectedThisSession(walletId) } returns false
|
||||
|
||||
useCase(
|
||||
walletId = walletId,
|
||||
hasBalance = true,
|
||||
shouldShowUpgradeBanner = true,
|
||||
)
|
||||
|
||||
coVerify(exactly = 0) { hotWalletRepository.setHasHadFirstTopUp(any(), any()) }
|
||||
coVerify(exactly = 0) { hotWalletRepository.setShouldShowUpgradeBanner(any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN multiple re-emissions with same state WHEN invoke THEN return same result`() = runTest {
|
||||
val creationTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31)
|
||||
coEvery { hotWalletRepository.getWalletCreationTimestamp(walletId) } returns creationTimestamp
|
||||
coEvery { hotWalletRepository.getUpgradeBannerClosureTimestamp(walletId) } returns null
|
||||
coEvery { hotWalletRepository.hasHadFirstTopUp(walletId) } returns false
|
||||
every { hotWalletRepository.isFirstTopUpDetectedThisSession(walletId) } returns false
|
||||
|
||||
val result1 = useCase(
|
||||
walletId = walletId,
|
||||
hasBalance = false,
|
||||
shouldShowUpgradeBanner = false,
|
||||
)
|
||||
val result2 = useCase(
|
||||
walletId = walletId,
|
||||
hasBalance = false,
|
||||
shouldShowUpgradeBanner = false,
|
||||
)
|
||||
val result3 = useCase(
|
||||
walletId = walletId,
|
||||
hasBalance = false,
|
||||
shouldShowUpgradeBanner = false,
|
||||
)
|
||||
|
||||
assertThat((result1 as Either.Right).value).isTrue()
|
||||
assertThat((result2 as Either.Right).value).isTrue()
|
||||
assertThat((result3 as Either.Right).value).isTrue()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.domain.hotwallet
|
||||
|
||||
import arrow.core.Either
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.hotwallet.repository.HotWalletRepository
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class CloseHotWalletUpgradeBannerUseCaseTest {
|
||||
|
||||
private val hotWalletRepository: HotWalletRepository = mockk(relaxed = true)
|
||||
private val useCase = CloseHotWalletUpgradeBannerUseCase(hotWalletRepository)
|
||||
|
||||
private val walletId = UserWalletId("0123456789ABCDEF")
|
||||
|
||||
@Test
|
||||
fun `WHEN invoke THEN set banner flag to false and closure timestamp`() = runTest {
|
||||
val result = useCase(walletId)
|
||||
|
||||
assertThat(result).isInstanceOf(Either.Right::class.java)
|
||||
coVerify { hotWalletRepository.setShouldShowUpgradeBanner(walletId, false) }
|
||||
coVerify { hotWalletRepository.setUpgradeBannerClosureTimestamp(walletId, any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN repository throws exception WHEN invoke THEN return Either Left`() = runTest {
|
||||
val exception = RuntimeException("Test error")
|
||||
coEvery { hotWalletRepository.setShouldShowUpgradeBanner(walletId, false) } throws exception
|
||||
|
||||
val result = useCase(walletId)
|
||||
|
||||
assertThat(result).isInstanceOf(Either.Left::class.java)
|
||||
assertThat((result as Either.Left).value).isEqualTo(exception)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ import com.tangem.domain.common.wallets.UserWalletsListRepository
|
|||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.hotwallet.repository.HotWalletRepository
|
||||
import com.tangem.domain.hotwallet.CloseHotWalletUpgradeBannerUseCase
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -135,7 +135,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
private val getWalletsListForEnablingUseCase: GetWalletsForAutomaticallyPushEnablingUseCase,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val reviewManager: ReviewManager,
|
||||
private val hotWalletRepository: HotWalletRepository,
|
||||
private val closeHotWalletUpgradeBannerUseCase: CloseHotWalletUpgradeBannerUseCase,
|
||||
) : BaseWalletClickIntents(), WalletWarningsClickIntents {
|
||||
|
||||
override fun onAddBackupCardClick() {
|
||||
|
|
@ -554,8 +554,6 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
modelScope.launch(dispatchers.main) {
|
||||
val userWallet = getUserWalletUseCase(userWalletId).getOrNull()
|
||||
if (userWallet is UserWallet.Hot) {
|
||||
hotWalletRepository.setShouldShowUpgradeBanner(userWalletId, false)
|
||||
hotWalletRepository.setShouldShowNextTimeUpgradeBanner(userWalletId, false)
|
||||
appRouter.push(UpgradeWallet(userWalletId))
|
||||
}
|
||||
}
|
||||
|
|
@ -565,15 +563,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
modelScope.launch(dispatchers.main) {
|
||||
val userWallet = getUserWalletUseCase(userWalletId).getOrNull()
|
||||
if (userWallet is UserWallet.Hot) {
|
||||
val hasHadFirstTopUp = hotWalletRepository.hasHadFirstTopUp(userWalletId)
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
if (hasHadFirstTopUp) {
|
||||
hotWalletRepository.setShouldShowUpgradeBanner(userWalletId, false)
|
||||
hotWalletRepository.setUpgradeBannerClosureTimestamp(userWalletId, currentTime)
|
||||
} else {
|
||||
hotWalletRepository.setShouldShowUpgradeBanner(userWalletId, true)
|
||||
}
|
||||
closeHotWalletUpgradeBannerUseCase(userWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,9 @@ import com.tangem.domain.card.common.util.cardTypesResolver
|
|||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.hotwallet.CheckHotWalletUpgradeBannerUseCase
|
||||
import com.tangem.domain.hotwallet.GetAccessCodeSkippedUseCase
|
||||
import com.tangem.domain.hotwallet.repository.HotWalletRepository
|
||||
import com.tangem.domain.hotwallet.ShouldShowUpgradeHotWalletBannerUseCase
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
|
@ -44,7 +45,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
|
|
@ -60,7 +61,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
private val notificationsRepository: NotificationsRepository,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
private val getAccessCodeSkippedUseCase: GetAccessCodeSkippedUseCase,
|
||||
private val hotWalletRepository: HotWalletRepository,
|
||||
private val shouldShowUpgradeHotWalletBannerUseCase: ShouldShowUpgradeHotWalletBannerUseCase,
|
||||
private val checkHotWalletUpgradeBannerUseCase: CheckHotWalletUpgradeBannerUseCase,
|
||||
) {
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "MagicNumber", "LongMethod")
|
||||
|
|
@ -101,8 +103,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
getAccessCodeSkippedUseCase(userWallet.walletId).distinctUntilChanged(),
|
||||
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.YieldPromo)
|
||||
.distinctUntilChanged(),
|
||||
hotWalletRepository.shouldShowUpgradeBanner(userWallet.walletId).distinctUntilChanged(),
|
||||
hotWalletRepository.shouldShowNextTimeUpgradeBanner(userWallet.walletId).distinctUntilChanged(),
|
||||
shouldShowUpgradeHotWalletBannerUseCase.invoke(userWallet.walletId)
|
||||
.distinctUntilChanged(),
|
||||
) { array -> array }
|
||||
.combine(tokenListFlow()) { array, any: Any -> arrayOf(any).plus(elements = array) }
|
||||
.map { array ->
|
||||
|
|
@ -117,7 +119,6 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
val shouldAccessCodeSkipped = array[6] as Boolean
|
||||
val shouldShowYieldPromo = array[7] as Boolean
|
||||
val shouldShowUpgradeBanner = array[8] as Boolean
|
||||
val shouldShowNextTimeUpgradeBanner = array[9] as Boolean
|
||||
|
||||
buildList {
|
||||
addUsedOutdatedDataNotification(totalFiatBalance)
|
||||
|
|
@ -129,7 +130,6 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
flattenCurrencies = flattenCurrencies,
|
||||
clickIntents = clickIntents,
|
||||
shouldShowUpgradeBanner = shouldShowUpgradeBanner,
|
||||
shouldShowNextTimeUpgradeBanner = shouldShowNextTimeUpgradeBanner,
|
||||
)
|
||||
|
||||
addFinishWalletActivationNotification(
|
||||
|
|
@ -464,45 +464,22 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private suspend fun MutableList<WalletNotification>.addUpgradeHotWalletPromoNotification(
|
||||
userWallet: UserWallet,
|
||||
flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
|
||||
clickIntents: WalletClickIntents,
|
||||
shouldShowUpgradeBanner: Boolean,
|
||||
shouldShowNextTimeUpgradeBanner: Boolean,
|
||||
) {
|
||||
if (userWallet !is UserWallet.Hot) return
|
||||
|
||||
val currentTime = System.currentTimeMillis()
|
||||
val creationTimestamp = hotWalletRepository.getWalletCreationTimestamp(userWallet.walletId)
|
||||
val closureTimestamp = hotWalletRepository.getUpgradeBannerClosureTimestamp(userWallet.walletId)
|
||||
val hasHadFirstTopUp = hotWalletRepository.hasHadFirstTopUp(userWallet.walletId)
|
||||
|
||||
if (creationTimestamp == null) {
|
||||
hotWalletRepository.setWalletCreationTimestamp(userWallet.walletId, currentTime)
|
||||
return
|
||||
}
|
||||
|
||||
val daysSinceCreation = TimeUnit.MILLISECONDS.toDays(currentTime - creationTimestamp)
|
||||
val daysSinceClosure = closureTimestamp?.let { TimeUnit.MILLISECONDS.toDays(currentTime - it) }
|
||||
|
||||
val currencies = flattenCurrencies.getOrNull(isPartialContentAccepted = true).orEmpty()
|
||||
val hasBalance = currencies.any { it.value.amount.orZero().isPositive() }
|
||||
|
||||
if (hasBalance && !hasHadFirstTopUp) {
|
||||
hotWalletRepository.setHasHadFirstTopUp(userWallet.walletId, true)
|
||||
}
|
||||
|
||||
val shouldShow = when {
|
||||
shouldShowUpgradeBanner && hasBalance -> true
|
||||
shouldShowNextTimeUpgradeBanner && daysSinceClosure != null && daysSinceClosure >= UPGRADE_BANNER_RESHOW_DAYS -> true
|
||||
!shouldShowUpgradeBanner && !shouldShowNextTimeUpgradeBanner && hasHadFirstTopUp && daysSinceCreation >= UPGRADE_BANNER_RESHOW_DAYS -> {
|
||||
hotWalletRepository.setShouldShowNextTimeUpgradeBanner(userWallet.walletId, true)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
val shouldShow = checkHotWalletUpgradeBannerUseCase(
|
||||
walletId = userWallet.walletId,
|
||||
hasBalance = hasBalance,
|
||||
shouldShowUpgradeBanner = shouldShowUpgradeBanner,
|
||||
).getOrNull() ?: return
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.UpgradeHotWalletPromo(
|
||||
|
|
@ -515,6 +492,5 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
|
||||
private companion object {
|
||||
const val MAX_REMAINING_SIGNATURES_COUNT = 10
|
||||
const val UPGRADE_BANNER_RESHOW_DAYS = 30
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue