diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index 4096521b75..b153b0e0de 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -6,6 +6,8 @@ import com.tangem.domain.transaction.usecase.ParseSharedAddressUseCase import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.delegate.DefaultUserWalletsSyncDelegate +import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository import com.tangem.domain.wallets.repository.WalletsRepository @@ -28,6 +30,17 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) internal object WalletsDomainModule { + @Provides + fun providesUserWalletsSyncDelegate( + userWalletsListManager: UserWalletsListManager, + dispatchers: CoroutineDispatcherProvider, + ): UserWalletsSyncDelegate { + return DefaultUserWalletsSyncDelegate( + userWalletsListManager = userWalletsListManager, + dispatchers = dispatchers, + ) + } + @Provides @Singleton fun providesGetWalletsUseCase(userWalletsListManager: UserWalletsListManager): GetWalletsUseCase { @@ -102,10 +115,13 @@ internal object WalletsDomainModule { @Provides @Singleton fun providesRenameWalletUseCase( - userWalletsListManager: UserWalletsListManager, - dispatchers: CoroutineDispatcherProvider, + walletsRepository: WalletsRepository, + userWalletsSyncDelegate: UserWalletsSyncDelegate, ): RenameWalletUseCase { - return RenameWalletUseCase(userWalletsListManager = userWalletsListManager, dispatchers = dispatchers) + return RenameWalletUseCase( + walletsRepository = walletsRepository, + userWalletsSyncDelegate = userWalletsSyncDelegate, + ) } @Provides @@ -197,4 +213,16 @@ internal object WalletsDomainModule { nftFeatureToggles = nftFeatureToggles, ) } + + @Provides + @Singleton + fun providesUpdateRemoteWalletsInfoUseCase( + walletsRepository: WalletsRepository, + userWalletsSyncDelegate: UserWalletsSyncDelegate, + ): UpdateRemoteWalletsInfoUseCase { + return UpdateRemoteWalletsInfoUseCase( + walletsRepository = walletsRepository, + userWalletsSyncDelegate = userWalletsSyncDelegate, + ) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index 7b474dd442..d4db8f1fa1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -172,6 +172,9 @@ interface TangemTechApi { @GET("user-wallets/wallets/{wallet_id}") suspend fun getWalletById(@Path("wallet_id") walletId: String): ApiResponse + + @GET("user-wallets/wallets/by-app/{app_id}") + suspend fun getWallets(@Path("app_id") appId: String): ApiResponse> // endregion companion object { diff --git a/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt b/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt index 1470589ef2..62f837994d 100644 --- a/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt +++ b/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt @@ -4,12 +4,12 @@ import com.tangem.data.notifications.converters.NotificationsEligibleNetworkConv import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.models.NotificationApplicationCreateBody -import com.tangem.datasource.api.tangemTech.models.WalletBody import com.tangem.datasource.api.tangemTech.models.WalletIdBody import com.tangem.utils.info.AppInfoProvider import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.* +import com.tangem.domain.notifications.models.ApplicationId import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.domain.notifications.models.NotificationsEligibleNetwork import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -23,7 +23,7 @@ internal class DefaultNotificationsRepository @Inject constructor( private val dispatchers: CoroutineDispatcherProvider, ) : NotificationsRepository { - override suspend fun createApplicationId(pushToken: String?): String = withContext(dispatchers.io) { + override suspend fun createApplicationId(pushToken: String?): ApplicationId = withContext(dispatchers.io) { tangemTechApi.createApplicationId( NotificationApplicationCreateBody( platform = appInfoProvider.platform, @@ -33,15 +33,16 @@ internal class DefaultNotificationsRepository @Inject constructor( timezone = appInfoProvider.timezone, pushToken = pushToken, ), - ).getOrThrow().appId + ).getOrThrow().appId.let(::ApplicationId) } - override suspend fun saveApplicationId(appId: String) { - appPreferencesStore.store(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY, appId) + override suspend fun saveApplicationId(appId: ApplicationId) { + appPreferencesStore.store(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY, appId.value) } - override suspend fun getApplicationId(): String? { + override suspend fun getApplicationId(): ApplicationId? { return appPreferencesStore.getSyncOrNull(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY) + ?.let(::ApplicationId) } override suspend fun incrementTronTokenFeeNotificationShowCounter() { @@ -71,21 +72,10 @@ internal class DefaultNotificationsRepository @Inject constructor( ).getOrThrow() } - override suspend fun setWalletName(walletId: String, walletName: String) = withContext(dispatchers.io) { - tangemTechApi.updateWallet( - walletId, - WalletBody(name = walletName), - ).getOrThrow() - } - - override suspend fun getWalletName(walletId: String): String? = withContext(dispatchers.io) { - tangemTechApi.getWalletById(walletId).getOrThrow().name - } - - override suspend fun sendPushToken(appId: String, pushToken: String) { + override suspend fun sendPushToken(appId: ApplicationId, pushToken: String) { withContext(dispatchers.io) { tangemTechApi.updatePushTokenForApplicationId( - appId, + appId.value, NotificationApplicationCreateBody( pushToken = pushToken, ), diff --git a/data/notifications/src/test/java/com/tangem/data/notifications/DefaultNotificationsRepositoryTest.kt b/data/notifications/src/test/java/com/tangem/data/notifications/DefaultNotificationsRepositoryTest.kt index 305732cde6..57d8d4c429 100644 --- a/data/notifications/src/test/java/com/tangem/data/notifications/DefaultNotificationsRepositoryTest.kt +++ b/data/notifications/src/test/java/com/tangem/data/notifications/DefaultNotificationsRepositoryTest.kt @@ -12,6 +12,7 @@ import com.squareup.moshi.Moshi import com.tangem.data.notifications.converters.NotificationsEligibleNetworkConverter import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.tangemTech.models.* +import com.tangem.domain.notifications.models.ApplicationId import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider import io.mockk.coEvery import io.mockk.coVerify @@ -41,9 +42,9 @@ class DefaultNotificationsRepositoryTest { fun `GIVEN valid push token WHEN createApplicationId THEN returns application id`() = runTest { // GIVEN val pushToken = "test-push-token" - val expectedAppId = "test-app-id" + val expectedAppId = ApplicationId("test-app-id") val expectedAppIdResponse = NotificationApplicationIdResponse( - appId = expectedAppId, + appId = expectedAppId.value, ) coEvery { appInfoProvider.platform } returns "android" coEvery { appInfoProvider.device } returns "test-device" @@ -76,7 +77,7 @@ class DefaultNotificationsRepositoryTest { @Test fun `GIVEN application id WHEN saveApplicationId THEN stores it in preferences`() = runTest { // GIVEN - val appId = "test-app-id" + val appId = ApplicationId("test-app-id") val preferences = mockk(relaxed = true) coEvery { preferencesDataStore.updateData(any()) } returns preferences @@ -90,10 +91,10 @@ class DefaultNotificationsRepositoryTest { @Test fun `GIVEN stored application id WHEN getApplicationId THEN returns it`() = runTest { // GIVEN - val expectedAppId = "test-app-id" + val expectedAppId = ApplicationId("test-app-id") val preferences = mockk(relaxed = true) val key = stringPreferencesKey(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY.name) - every { preferences[key] } returns expectedAppId + every { preferences[key] } returns expectedAppId.value coEvery { preferencesDataStore.data } returns flowOf(preferences) // WHEN @@ -122,56 +123,24 @@ class DefaultNotificationsRepositoryTest { coVerify { tangemTechApi.associateApplicationIdWithWallets(appId, wallets.map { WalletIdBody(it) }) } } - @Test - fun `GIVEN wallet id and name WHEN setWalletName THEN updates wallet name`() = runTest { - // GIVEN - val walletId = "test-wallet-id" - val walletName = "Test Wallet" - coEvery { - tangemTechApi.updateWallet( - walletId, - WalletBody(name = walletName), - ) - } returns ApiResponse.Success(Unit) - - // WHEN - repository.setWalletName(walletId, walletName) - - // THEN - coVerify { tangemTechApi.updateWallet(walletId, WalletBody(name = walletName)) } - } - - @Test - fun `GIVEN wallet id WHEN getWalletName THEN returns wallet name`() = runTest { - // GIVEN - val walletId = "test-wallet-id" - val expectedName = "Test Wallet" - coEvery { tangemTechApi.getWalletById(walletId) } returns ApiResponse.Success( - WalletResponse( - notifyStatus = false, - name = expectedName, - id = walletId, - ), - ) - - // WHEN - val result = repository.getWalletName(walletId) - - // THEN - assertThat(result).isEqualTo(expectedName) - } - @Test fun `GIVEN application id and push token WHEN sendPushToken THEN updates push token`() = runTest { // GIVEN - val appId = "test-app-id" + val appId = ApplicationId("test-app-id") val pushToken = "test-push-token" coEvery { tangemTechApi.updatePushTokenForApplicationId( - appId, - NotificationApplicationCreateBody(pushToken = pushToken), + appId.value, + NotificationApplicationCreateBody( + pushToken = pushToken, + platform = null, + device = null, + systemVersion = null, + language = null, + timezone = null, + ), ) - } returns ApiResponse.Success(appId) + } returns ApiResponse.Success(appId.value) // WHEN repository.sendPushToken(appId, pushToken) @@ -179,8 +148,15 @@ class DefaultNotificationsRepositoryTest { // THEN coVerify { tangemTechApi.updatePushTokenForApplicationId( - appId, - NotificationApplicationCreateBody(pushToken = pushToken), + appId.value, + NotificationApplicationCreateBody( + pushToken = pushToken, + platform = null, + device = null, + systemVersion = null, + language = null, + timezone = null, + ), ) } } diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt index 39096c2551..25bc5ae0b6 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt @@ -1,5 +1,6 @@ package com.tangem.data.wallets +import com.tangem.data.wallets.converters.UserWalletRemoteInfoConverter import com.tangem.datasource.api.common.response.ApiResponseError.HttpException import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi @@ -18,6 +19,7 @@ import com.tangem.datasource.local.preferences.utils.store import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UserWalletRemoteInfo import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.utils.WEEK_MILLIS import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -28,6 +30,7 @@ import kotlinx.coroutines.withContext typealias SeedPhraseNotificationsStatuses = Map +@Suppress("TooManyFunctions") internal class DefaultWalletsRepository( private val appPreferencesStore: AppPreferencesStore, private val tangemTechApi: TangemTechApi, @@ -254,6 +257,37 @@ internal class DefaultWalletsRepository( } } + override suspend fun setWalletName(walletId: String, walletName: String) = withContext(dispatchers.io) { + tangemTechApi.updateWallet( + walletId = walletId, + body = WalletBody(name = walletName), + ).getOrThrow() + } + + override suspend fun getWalletInfo(walletId: String): UserWalletRemoteInfo = withContext(dispatchers.io) { + UserWalletRemoteInfoConverter.convert( + value = tangemTechApi.getWalletById(walletId).getOrThrow(), + ) + } + + override suspend fun getWalletsInfo(applicationId: String, updateCache: Boolean): List = + withContext(dispatchers.io) { + tangemTechApi.getWallets(applicationId) + .getOrThrow() + .map { walletInfo -> + val userWallet = UserWalletRemoteInfoConverter.convert( + value = walletInfo, + ) + if (updateCache) { + setNotificationsEnabledLocally( + userWalletId = userWallet.walletId, + isEnabled = userWallet.isNotificationsEnabled, + ) + } + userWallet + } + } + private suspend fun loadAndSaveNotificationsEnabled(userWalletId: UserWalletId): Boolean { val walletResponse = tangemTechApi.getWalletById(walletId = userWalletId.stringValue).getOrThrow() val isEnabled = walletResponse.notifyStatus diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/converters/UserWalletRemoteInfoConverter.kt b/data/wallets/src/main/java/com/tangem/data/wallets/converters/UserWalletRemoteInfoConverter.kt new file mode 100644 index 0000000000..4ac595663e --- /dev/null +++ b/data/wallets/src/main/java/com/tangem/data/wallets/converters/UserWalletRemoteInfoConverter.kt @@ -0,0 +1,16 @@ +package com.tangem.data.wallets.converters + +import com.tangem.datasource.api.tangemTech.models.WalletResponse +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UserWalletRemoteInfo +import com.tangem.utils.converter.Converter + +internal object UserWalletRemoteInfoConverter : Converter { + override fun convert(value: WalletResponse): UserWalletRemoteInfo { + return UserWalletRemoteInfo( + walletId = UserWalletId(value.id), + name = value.name.orEmpty(), + isNotificationsEnabled = value.notifyStatus, + ) + } +} \ No newline at end of file diff --git a/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt b/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt index 28f07454c2..f430dab62f 100644 --- a/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt +++ b/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt @@ -173,4 +173,62 @@ class DefaultWalletsRepositoryTest { } coVerify(exactly = 1) { preferencesDataStore.updateData(any()) } } + + @Test + fun `GIVEN API returns wallets WHEN getWalletsInfo THEN should return converted wallets and update cache if requested`() = runTest { + // GIVEN + val applicationId = "test_app_id" + val wallet1Id = "1234567890abcdef" + val wallet2Id = "fedcba0987654321" + val walletResponses = listOf( + WalletResponse( + id = wallet1Id, + notifyStatus = true, + ), + WalletResponse( + id = wallet2Id, + notifyStatus = false, + ), + ) + coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses) + coEvery { preferencesDataStore.updateData(any()) } returns mockk() + + // WHEN + val result = repository.getWalletsInfo(applicationId, updateCache = true) + + // THEN + assertThat(result).hasSize(2) + assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id) + assertThat(result[0].isNotificationsEnabled).isTrue() + assertThat(result[1].walletId.stringValue).isEqualTo(wallet2Id) + assertThat(result[1].isNotificationsEnabled).isFalse() + + coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) } + coVerify(exactly = 2) { preferencesDataStore.updateData(any()) } + } + + @Test + fun `GIVEN API returns wallets WHEN getWalletsInfo with updateCache false THEN should return converted wallets without updating cache`() = runTest { + // GIVEN + val applicationId = "test_app_id" + val wallet1Id = "1234567890abcdef" + val walletResponses = listOf( + WalletResponse( + id = wallet1Id, + notifyStatus = true, + ), + ) + coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses) + + // WHEN + val result = repository.getWalletsInfo(applicationId, updateCache = false) + + // THEN + assertThat(result).hasSize(1) + assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id) + assertThat(result[0].isNotificationsEnabled).isTrue() + + coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) } + coVerify(exactly = 0) { preferencesDataStore.updateData(any()) } + } } \ No newline at end of file diff --git a/domain/notifications/models/src/main/java/com/tangem/domain/notifications/models/ApplicationId.kt b/domain/notifications/models/src/main/java/com/tangem/domain/notifications/models/ApplicationId.kt new file mode 100644 index 0000000000..62db4696b3 --- /dev/null +++ b/domain/notifications/models/src/main/java/com/tangem/domain/notifications/models/ApplicationId.kt @@ -0,0 +1,4 @@ +package com.tangem.domain.notifications.models + +@JvmInline +value class ApplicationId(val value: String) \ No newline at end of file diff --git a/domain/notifications/src/main/java/com/tangem/domain/notifications/GetApplicationIdUseCase.kt b/domain/notifications/src/main/java/com/tangem/domain/notifications/GetApplicationIdUseCase.kt index 026d3a89c6..963d3b99d7 100644 --- a/domain/notifications/src/main/java/com/tangem/domain/notifications/GetApplicationIdUseCase.kt +++ b/domain/notifications/src/main/java/com/tangem/domain/notifications/GetApplicationIdUseCase.kt @@ -1,6 +1,7 @@ package com.tangem.domain.notifications import arrow.core.Either +import com.tangem.domain.notifications.models.ApplicationId import com.tangem.domain.notifications.repository.NotificationsRepository import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -10,7 +11,7 @@ class GetApplicationIdUseCase( ) { private val mutex = Mutex() - suspend operator fun invoke(): Either = Either.catch { + suspend operator fun invoke(): Either = Either.catch { val localApplicationId = notificationsRepository.getApplicationId() if (localApplicationId != null) return@catch localApplicationId diff --git a/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt b/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt index b7f53ab51e..d5e99ab745 100644 --- a/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt +++ b/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt @@ -1,15 +1,16 @@ package com.tangem.domain.notifications.repository +import com.tangem.domain.notifications.models.ApplicationId import com.tangem.domain.notifications.models.NotificationsEligibleNetwork interface NotificationsRepository { @Throws - suspend fun createApplicationId(pushToken: String? = null): String + suspend fun createApplicationId(pushToken: String? = null): ApplicationId - suspend fun saveApplicationId(appId: String) + suspend fun saveApplicationId(appId: ApplicationId) - suspend fun getApplicationId(): String? + suspend fun getApplicationId(): ApplicationId? suspend fun getTronTokenFeeNotificationShowCounter(): Int @@ -19,13 +20,7 @@ interface NotificationsRepository { suspend fun associateApplicationIdWithWallets(appId: String, wallets: List) @Throws - suspend fun setWalletName(walletId: String, walletName: String) - - @Throws - suspend fun getWalletName(walletId: String): String? - - @Throws - suspend fun sendPushToken(appId: String, pushToken: String) + suspend fun sendPushToken(appId: ApplicationId, pushToken: String) @Throws suspend fun getEligibleNetworks(): List diff --git a/domain/notifications/src/test/java/com/tangem/domain/notifications/GetApplicationIdUseCaseTest.kt b/domain/notifications/src/test/java/com/tangem/domain/notifications/GetApplicationIdUseCaseTest.kt index 350f5bad73..6b2984554b 100644 --- a/domain/notifications/src/test/java/com/tangem/domain/notifications/GetApplicationIdUseCaseTest.kt +++ b/domain/notifications/src/test/java/com/tangem/domain/notifications/GetApplicationIdUseCaseTest.kt @@ -2,6 +2,7 @@ package com.tangem.domain.notifications import arrow.core.Either import com.google.common.truth.Truth.assertThat +import com.tangem.domain.notifications.models.ApplicationId import com.tangem.domain.notifications.repository.NotificationsRepository import io.mockk.coEvery import io.mockk.coVerify @@ -20,7 +21,7 @@ class GetApplicationIdUseCaseTest { @Test fun `GIVEN local application ID exists WHEN invoke THEN return local application ID`() = runTest { // GIVEN - val expectedApplicationId = "test-app-id" + val expectedApplicationId = ApplicationId("test-app-id") coEvery { notificationsRepository.getApplicationId() } returns expectedApplicationId // WHEN @@ -39,7 +40,7 @@ class GetApplicationIdUseCaseTest { @Test fun `GIVEN local application ID does not exist WHEN invoke THEN create and save new application ID`() = runTest { // GIVEN - val newApplicationId = "new-app-id" + val newApplicationId = ApplicationId("new-app-id") coEvery { notificationsRepository.getApplicationId() } returns null coEvery { notificationsRepository.createApplicationId() } returns newApplicationId coEvery { notificationsRepository.saveApplicationId(newApplicationId) } returns Unit @@ -81,7 +82,7 @@ class GetApplicationIdUseCaseTest { fun `GIVEN no local application ID WHEN multiple concurrent invokes THEN create only one application ID`() = runTest { // GIVEN - val newApplicationId = "new-app-id" + val newApplicationId = ApplicationId("new-app-id") var isIdCreated = false coEvery { notificationsRepository.getApplicationId() } answers { @@ -115,7 +116,7 @@ class GetApplicationIdUseCaseTest { @Test fun `GIVEN no local application ID WHEN multiple concurrent invokes with delay THEN create only one application ID`() = runTest { // GIVEN - val newApplicationId = "new-app-id" + val newApplicationId = ApplicationId("new-app-id") var isIdCreated = false coEvery { notificationsRepository.getApplicationId() } answers { diff --git a/domain/notifications/src/test/java/com/tangem/domain/notifications/SendPushTokenUseCaseTest.kt b/domain/notifications/src/test/java/com/tangem/domain/notifications/SendPushTokenUseCaseTest.kt index aeb18310a1..f079a021d1 100644 --- a/domain/notifications/src/test/java/com/tangem/domain/notifications/SendPushTokenUseCaseTest.kt +++ b/domain/notifications/src/test/java/com/tangem/domain/notifications/SendPushTokenUseCaseTest.kt @@ -2,6 +2,7 @@ package com.tangem.domain.notifications import arrow.core.Either import com.google.common.truth.Truth.assertThat +import com.tangem.domain.notifications.models.ApplicationId import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.utils.notifications.PushNotificationsTokenProvider import io.mockk.coEvery @@ -33,7 +34,7 @@ class SendPushTokenUseCaseTest { @Test fun `GIVEN valid application ID and token WHEN invoke THEN token is sent successfully`() = runTest { // GIVEN - val applicationId = "test-app-id" + val applicationId = ApplicationId("test-app-id") val token = "test-token" coEvery { getApplicationIdUseCase() } returns Either.Right(applicationId) coEvery { pushNotificationsTokenProvider.getToken() } returns token @@ -62,7 +63,7 @@ class SendPushTokenUseCaseTest { @Test fun `GIVEN repository throws error WHEN invoke THEN returns error`() = runTest { // GIVEN - val applicationId = "test-app-id" + val applicationId = ApplicationId("test-app-id") val token = "test-token" val expectedError = RuntimeException("Network error") coEvery { getApplicationIdUseCase() } returns Either.Right(applicationId) diff --git a/domain/wallets/build.gradle.kts b/domain/wallets/build.gradle.kts index 6ca312b904..331335a257 100644 --- a/domain/wallets/build.gradle.kts +++ b/domain/wallets/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { implementation(projects.domain.tokens) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) + implementation(projects.domain.notifications.models) // endregion // region Tangem libraries @@ -37,4 +38,11 @@ dependencies { implementation(deps.hilt.android) kapt(deps.hilt.kapt) // end + + // region Tests + testImplementation(deps.test.junit) + testImplementation(deps.test.coroutine) + testImplementation(deps.test.truth) + testImplementation(deps.test.mockk) + // end } \ No newline at end of file diff --git a/domain/wallets/models/src/main/java/com/tangem/domain/wallets/models/UserWalletRemoteInfo.kt b/domain/wallets/models/src/main/java/com/tangem/domain/wallets/models/UserWalletRemoteInfo.kt new file mode 100644 index 0000000000..5c6e3669a5 --- /dev/null +++ b/domain/wallets/models/src/main/java/com/tangem/domain/wallets/models/UserWalletRemoteInfo.kt @@ -0,0 +1,7 @@ +package com.tangem.domain.wallets.models + +class UserWalletRemoteInfo( + val walletId: UserWalletId, + val name: String, + val isNotificationsEnabled: Boolean, +) \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/delegate/DefaultUserWalletsSyncDelegate.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/delegate/DefaultUserWalletsSyncDelegate.kt new file mode 100644 index 0000000000..b1c1e11c7e --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/delegate/DefaultUserWalletsSyncDelegate.kt @@ -0,0 +1,55 @@ +package com.tangem.domain.wallets.delegate + +import arrow.core.Either +import arrow.core.raise.either +import arrow.core.raise.ensure +import com.tangem.common.CompletionResult +import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.models.UpdateWalletError +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UserWalletRemoteInfo +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext + +class DefaultUserWalletsSyncDelegate( + private val userWalletsListManager: UserWalletsListManager, + private val dispatchers: CoroutineDispatcherProvider, +) : UserWalletsSyncDelegate { + + override suspend fun syncWallet(userWalletId: UserWalletId, name: String): Either { + return renameUserWallet(userWalletId, name) + } + + override suspend fun syncWallets(list: List): Either = either { + list.forEach { userWallet -> + renameUserWallet(userWallet.walletId, userWallet.name).bind() + } + } + + private suspend fun renameUserWallet( + userWalletId: UserWalletId, + name: String, + ): Either = withContext(dispatchers.io) { + either { + val existingNames = userWalletsListManager.userWalletsSync + + ensure(existingNames.none { it.name == name && it.walletId != userWalletId }) { + UpdateWalletError.NameAlreadyExists + } + + val previousName = existingNames.firstOrNull { it.walletId == userWalletId }?.name.orEmpty() + if (previousName == name) { + raise(UpdateWalletError.NameAlreadyExists) + } + + return@withContext when ( + val result = + userWalletsListManager.update(userWalletId) { it.copy(name = name) } + ) { + is CompletionResult.Failure -> raise(UpdateWalletError.DataError(result.error)) + is CompletionResult.Success -> result.data + } + } + } +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/delegate/UserWalletsSyncDelegate.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/delegate/UserWalletsSyncDelegate.kt new file mode 100644 index 0000000000..94f72df038 --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/delegate/UserWalletsSyncDelegate.kt @@ -0,0 +1,14 @@ +package com.tangem.domain.wallets.delegate + +import arrow.core.Either +import com.tangem.domain.wallets.models.UpdateWalletError +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UserWalletRemoteInfo + +interface UserWalletsSyncDelegate { + + suspend fun syncWallet(userWalletId: UserWalletId, name: String): Either + + suspend fun syncWallets(list: List): Either +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt index 541e601c54..90768f5e78 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt @@ -2,8 +2,10 @@ package com.tangem.domain.wallets.repository import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UserWalletRemoteInfo import kotlinx.coroutines.flow.Flow +@Suppress("TooManyFunctions") interface WalletsRepository { suspend fun shouldSaveUserWalletsSync(): Boolean @@ -43,4 +45,13 @@ interface WalletsRepository { @Throws suspend fun setNotificationsEnabled(userWalletId: UserWalletId, isEnabled: Boolean) + + @Throws + suspend fun setWalletName(walletId: String, walletName: String) + + @Throws + suspend fun getWalletInfo(walletId: String): UserWalletRemoteInfo + + @Throws + suspend fun getWalletsInfo(applicationId: String, updateCache: Boolean = true): List } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/RenameWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/RenameWalletUseCase.kt index ff568c6794..8225cd9220 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/RenameWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/RenameWalletUseCase.kt @@ -2,38 +2,23 @@ package com.tangem.domain.wallets.usecase import arrow.core.Either import arrow.core.raise.either -import arrow.core.raise.ensure -import com.tangem.common.CompletionResult -import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate import com.tangem.domain.wallets.models.UpdateWalletError import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.withContext +import com.tangem.domain.wallets.repository.WalletsRepository -/** - * Use case for rename user wallet - * - * @property userWalletsListManager user wallets list manager - */ class RenameWalletUseCase( - private val userWalletsListManager: UserWalletsListManager, - private val dispatchers: CoroutineDispatcherProvider, + private val walletsRepository: WalletsRepository, + private val userWalletsSyncDelegate: UserWalletsSyncDelegate, ) { suspend operator fun invoke(userWalletId: UserWalletId, name: String): Either = - withContext(dispatchers.io) { - either { - val existingNames = userWalletsListManager.userWalletsSync - - ensure(existingNames.none { it.name == name && it.walletId != userWalletId }) { - UpdateWalletError.NameAlreadyExists - } - - when (val result = userWalletsListManager.update(userWalletId) { it.copy(name = name) }) { - is CompletionResult.Failure -> raise(UpdateWalletError.DataError(result.error)) - is CompletionResult.Success -> result.data - } + either { + runCatching { + walletsRepository.setWalletName(userWalletId.stringValue, name) } + + userWalletsSyncDelegate.syncWallet(userWalletId, name).bind() } } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UpdateRemoteWalletsInfoUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UpdateRemoteWalletsInfoUseCase.kt new file mode 100644 index 0000000000..5305089cd7 --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UpdateRemoteWalletsInfoUseCase.kt @@ -0,0 +1,19 @@ +package com.tangem.domain.wallets.usecase + +import arrow.core.Either +import arrow.core.raise.either +import com.tangem.domain.notifications.models.ApplicationId +import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate +import com.tangem.domain.wallets.models.UpdateWalletError +import com.tangem.domain.wallets.repository.WalletsRepository + +class UpdateRemoteWalletsInfoUseCase( + private val walletsRepository: WalletsRepository, + private val userWalletsSyncDelegate: UserWalletsSyncDelegate, +) { + + suspend operator fun invoke(applicationId: ApplicationId): Either = either { + val walletsInfo = walletsRepository.getWalletsInfo(applicationId.value) + userWalletsSyncDelegate.syncWallets(walletsInfo).bind() + } +} \ No newline at end of file