diff --git a/.gitignore b/.gitignore index 3e96fb9767..74ed0fd34b 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ local.properties **/fastlane/Preview.html **/fastlane/screenshots **/fastlane/test_output + +# Kotlin Plugin +.kotlin/ diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f3177619a4..65f7822aff 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -10,6 +10,7 @@ plugins { alias(deps.plugins.hilt.android) alias(deps.plugins.firebase.crashlytics) alias(deps.plugins.firebase.perf) + alias(deps.plugins.ksp) id("configuration") } @@ -22,9 +23,15 @@ android { jniLibs { useLegacyPackaging = true } - resources.excludes.add("META-INF/DEPENDENCIES") resources.excludes.add("META-INF/LICENSE.md") resources.excludes.add("META-INF/NOTICE.md") + resources.excludes.add("META-INF/DISCLAIMER") + resources.excludes.add("META-INF/DEPENDENCIES") + resources.excludes.add("META-INF/FastDoubleParser-NOTICE") + resources.excludes.add("META-INF/FastDoubleParser-LICENSE") + resources.excludes.add("META-INF/io.netty.versions.properties") + resources.excludes.add("META-INF/INDEX.LIST") + resources.excludes.add("META-INF/versions/9/OSGI-INF/MANIFEST.MF") } androidResources { generateLocaleConfig = true @@ -141,7 +148,7 @@ dependencies { /** Features */ implementation(projects.features.onboarding) - implementation(projects.features.referral.presentation) + implementation(projects.features.referral.impl) implementation(projects.features.referral.domain) implementation(projects.features.referral.data) implementation(projects.features.swap.api) @@ -179,6 +186,12 @@ dependencies { implementation(projects.features.onboardingV2.impl) implementation(projects.features.stories.api) implementation(projects.features.stories.impl) + implementation(projects.features.txhistory.api) + implementation(projects.features.txhistory.impl) + implementation(projects.features.askBiometry.api) + implementation(projects.features.askBiometry.impl) + implementation(projects.features.nft.api) + implementation(projects.features.nft.impl) /** AndroidX libraries */ implementation(deps.androidx.core.ktx) @@ -263,7 +276,7 @@ dependencies { implementation(deps.moshi.adapters) implementation(deps.moshi.kotlin) - kaptForObfuscatingVariants(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) kaptForObfuscatingVariants(deps.retrofit.response.type.keeper) /** Testing libraries */ diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 89dac3cd1e..8e5de701e0 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 89dac3cd1e171d5801596ad151aa3af3fbc6c140 +Subproject commit 8e5de701e02a2d48a68e32cd4783549dde01983e diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index ed2a3f63b6..1f2e49db06 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -28,7 +28,7 @@ import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import arrow.core.getOrElse import by.kirich1409.viewbindingdelegate.viewBinding -import com.arkivanov.decompose.value.observe +import com.arkivanov.decompose.value.subscribe import com.arkivanov.essenty.lifecycle.asEssentyLifecycle import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar @@ -46,6 +46,7 @@ import com.tangem.core.ui.message.EventMessageEffect import com.tangem.core.ui.message.SnackbarMessage import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme +import com.tangem.data.balancehiding.DefaultDeviceFlipDetector import com.tangem.data.card.sdk.CardSdkOwner import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.card.ScanCardUseCase @@ -59,14 +60,9 @@ import com.tangem.domain.staking.SendUnsubmittedHashesUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles -import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION -import com.tangem.features.send.api.navigation.SendRouter -import com.tangem.features.tokendetails.navigation.TokenDetailsRouter -import com.tangem.features.wallet.navigation.WalletRouter import com.tangem.google.GoogleServicesHelper import com.tangem.operations.backup.BackupService import com.tangem.sdk.api.BackupServiceHolder @@ -138,21 +134,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var scanCardUseCase: ScanCardUseCase - @Inject - lateinit var walletRouter: WalletRouter - - @Inject - lateinit var tokenDetailsRouter: TokenDetailsRouter - @Inject lateinit var walletConnectInteractor: WalletConnectInteractor - @Inject - lateinit var sendRouter: SendRouter - - @Inject - lateinit var qrScanningRouter: QrScanningRouter - @Inject lateinit var deepLinksRegistry: DeepLinksRegistry @@ -187,9 +171,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject internal lateinit var routingComponentFactory: RoutingComponent.Factory - @Inject - lateinit var pushNotificationsRouter: PushNotificationsRouter - @Inject lateinit var cardRepository: CardRepository @@ -217,6 +198,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject internal lateinit var uiDependencies: UiDependencies + @Inject + internal lateinit var defaultDeviceFlipDetector: DefaultDeviceFlipDetector + internal val viewModel: MainViewModel by viewModels() private lateinit var appThemeModeFlow: SharedFlow @@ -283,6 +267,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } lifecycle.addObserver(WindowObscurationObserver) + lifecycle.addObserver(defaultDeviceFlipDetector) } private fun installEventMessageEffect() { @@ -336,12 +321,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac appRouterConfig.componentRouter = routingComponent.router appRouterConfig.snackbarHandler = this - routingComponent.stack.observe(lifecycle.asEssentyLifecycle()) { childStack -> + routingComponent.stack.subscribe(lifecycle.asEssentyLifecycle()) { childStack -> val stack = childStack.backStack .plus(childStack.active) .map { it.configuration } - if (stack == appRouterConfig.stack) return@observe + if (stack == appRouterConfig.stack) return@subscribe appRouterConfig.stack = stack diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index 217260e00b..fe2add9b71 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -113,6 +113,7 @@ sealed class AnalyticsParam { const val SEED_PHRASE_LENGTH = "Seed Phrase Length" const val DAPP_NAME = "DApp Name" const val DAPP_URL = "DApp Url" + const val NETWORKS = "Networks" const val METHOD_NAME = "Method Name" const val VALIDATION = "Validation" const val BLOCKCHAIN_EXCEPTION_HOST = "exception_host" diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt index d2870bbb6a..1725eabaaa 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt @@ -1,6 +1,7 @@ package com.tangem.tap.common.analytics.events import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType /** [REDACTED_AUTHOR] @@ -12,8 +13,41 @@ internal sealed class WalletConnect( ) : AnalyticsEvent("Wallet Connect", event, params, error) { class ScreenOpened : WalletConnect(event = "WC Screen Opened") - class NewSessionEstablished(dAppName: String, dAppUrl: String, blockchainNames: List) : WalletConnect( - event = "New Session Established", + class NewSessionInitiated(source: SourceType) : WalletConnect( + event = "Session Initiated", + params = mapOf( + AnalyticsParam.SOURCE to when (source) { + SourceType.QR -> "QR" + SourceType.DEEPLINK -> "DeepLink" + SourceType.ETC -> "etc" + }, + ), + ) + + data object SessionFailed : WalletConnect( + event = "Session Failed", + ) + + class DAppConnectionRequested( + blockchainNames: List, + ) : WalletConnect( + event = "dApp Connection Requested", + params = mapOf( + AnalyticsParam.NETWORKS to blockchainNames.joinToString(","), + ), + ) + + class DAppConnected(dAppName: String, dAppUrl: String, blockchainNames: List) : WalletConnect( + event = "dApp Connected", + params = mapOf( + AnalyticsParam.DAPP_NAME to dAppName, + AnalyticsParam.DAPP_URL to dAppUrl, + AnalyticsParam.BLOCKCHAIN to blockchainNames.joinToString(","), + ), + ) + + class DAppConnectionFailed(dAppName: String, dAppUrl: String, blockchainNames: List) : WalletConnect( + event = "dApp Connection Failed", params = mapOf( AnalyticsParam.DAPP_NAME to dAppName, AnalyticsParam.DAPP_URL to dAppUrl, @@ -22,17 +56,31 @@ internal sealed class WalletConnect( ) class SessionDisconnected(dAppName: String, dAppUrl: String) : WalletConnect( - event = "Session Disconnected", + event = "dApp Disconnected", params = mapOf( AnalyticsParam.DAPP_NAME to dAppName, AnalyticsParam.DAPP_URL to dAppUrl, ), ) - class RequestHandled( + class SignatureRequestHandled( params: RequestHandledParams, ) : WalletConnect( - event = "Request Handled", + event = "Signature Request Handled", + params = params.toParamsMap(), + ) + + class SignatureRequestReceived( + params: RequestHandledParams, + ) : WalletConnect( + event = "Signature Request Received", + params = params.toParamsMap(), + ) + + class SignatureRequestFailed( + params: RequestHandledParams, + ) : WalletConnect( + event = "Signature Request Failed", params = params.toParamsMap(), ) diff --git a/app/src/main/java/com/tangem/tap/common/compose/TangemTextFieldsDefault.kt b/app/src/main/java/com/tangem/tap/common/compose/TangemTextFieldsDefault.kt deleted file mode 100644 index 945ffc182f..0000000000 --- a/app/src/main/java/com/tangem/tap/common/compose/TangemTextFieldsDefault.kt +++ /dev/null @@ -1,152 +0,0 @@ -package com.tangem.tap.common.compose - -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.interaction.InteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -import androidx.compose.material.TextFieldColors -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.State -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.Color -import com.tangem.core.ui.res.TangemTheme - -// Copy from com.tangem.core.ui.components -// TODO: Delete this after fields has been moved to core-ui module -internal object TangemTextFieldsDefault { - val defaultTextFieldColors: TangemTextFieldColors - @Composable @Stable get() = TangemTextFieldColors( - textColor = TangemTheme.colors.text.primary1, - disabledTextColor = TangemTheme.colors.text.disabled, - backgroundColor = Color.Transparent, - cursorColor = TangemTheme.colors.icon.primary1, - errorCursorColor = TangemTheme.colors.icon.warning, - focusedIndicatorColor = TangemTheme.colors.icon.primary1, - unfocusedIndicatorColor = TangemTheme.colors.stroke.primary, - disabledIndicatorColor = TangemTheme.colors.stroke.primary, - errorIndicatorColor = TangemTheme.colors.icon.warning, - leadingIconColor = TangemTheme.colors.icon.informative, - disabledLeadingIconColor = Color.Transparent, - errorLeadingIconColor = TangemTheme.colors.icon.warning, - trailingIconColor = TangemTheme.colors.icon.informative, - disabledTrailingIconColor = Color.Transparent, - errorTrailingIconColor = TangemTheme.colors.icon.warning, - focusedLabelColor = TangemTheme.colors.text.primary1, - unfocusedLabelColor = TangemTheme.colors.text.secondary, - disabledLabelColor = TangemTheme.colors.text.disabled, - errorLabelColor = TangemTheme.colors.icon.warning, - placeholderColor = TangemTheme.colors.text.secondary, - disabledPlaceholderColor = TangemTheme.colors.text.disabled, - captionColor = TangemTheme.colors.text.tertiary, - disabledCaptionColor = TangemTheme.colors.text.disabled, - errorCaptionColor = TangemTheme.colors.icon.warning, - ) -} - -@Immutable -internal data class TangemTextFieldColors( - private val textColor: Color, - private val disabledTextColor: Color, - private val cursorColor: Color, - private val errorCursorColor: Color, - private val focusedIndicatorColor: Color, - private val unfocusedIndicatorColor: Color, - private val errorIndicatorColor: Color, - private val disabledIndicatorColor: Color, - private val leadingIconColor: Color, - private val disabledLeadingIconColor: Color, - private val errorLeadingIconColor: Color, - private val trailingIconColor: Color, - private val disabledTrailingIconColor: Color, - private val errorTrailingIconColor: Color, - private val backgroundColor: Color, - private val focusedLabelColor: Color, - private val unfocusedLabelColor: Color, - private val disabledLabelColor: Color, - private val errorLabelColor: Color, - private val placeholderColor: Color, - private val disabledPlaceholderColor: Color, - private val captionColor: Color, - private val disabledCaptionColor: Color, - private val errorCaptionColor: Color, -) : TextFieldColors { - - @Composable - override fun leadingIconColor(enabled: Boolean, isError: Boolean): State { - return rememberUpdatedState( - when { - !enabled -> disabledLeadingIconColor - isError -> errorLeadingIconColor - else -> leadingIconColor - }, - ) - } - - @Composable - override fun trailingIconColor(enabled: Boolean, isError: Boolean): State { - return rememberUpdatedState( - when { - !enabled -> disabledTrailingIconColor - isError -> errorTrailingIconColor - else -> trailingIconColor - }, - ) - } - - @Composable - override fun indicatorColor( - enabled: Boolean, - isError: Boolean, - interactionSource: InteractionSource, - ): State { - val focused by interactionSource.collectIsFocusedAsState() - - val targetValue = when { - !enabled -> disabledIndicatorColor - isError -> errorIndicatorColor - focused -> focusedIndicatorColor - else -> unfocusedIndicatorColor - } - return if (enabled) { - animateColorAsState(targetValue, tween(durationMillis = 120)) - } else { - rememberUpdatedState(targetValue) - } - } - - @Composable - override fun backgroundColor(enabled: Boolean): State { - return rememberUpdatedState(backgroundColor) - } - - @Composable - override fun placeholderColor(enabled: Boolean): State { - return rememberUpdatedState(if (enabled) placeholderColor else disabledPlaceholderColor) - } - - @Composable - override fun labelColor(enabled: Boolean, error: Boolean, interactionSource: InteractionSource): State { - val focused by interactionSource.collectIsFocusedAsState() - - val targetValue = when { - !enabled -> disabledLabelColor - error -> errorLabelColor - focused -> focusedLabelColor - else -> unfocusedLabelColor - } - return rememberUpdatedState(targetValue) - } - - @Composable - override fun textColor(enabled: Boolean): State { - return rememberUpdatedState(if (enabled) textColor else disabledTextColor) - } - - @Composable - override fun cursorColor(isError: Boolean): State { - return rememberUpdatedState(if (isError) errorCursorColor else cursorColor) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt b/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt deleted file mode 100644 index aef0701282..0000000000 --- a/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.tangem.tap.common.extensions - -import android.view.View -import android.widget.EditText -import android.widget.TextSwitcher -import android.widget.TextView -import com.google.android.material.textfield.TextInputLayout - -/** -[REDACTED_AUTHOR] - */ -fun TextView.update(text: String?) { - if (this.text?.toString() != text) this.text = text -} - -fun EditText.update(text: String?) { - if (this.text?.toString() == text) return - - val textLength = text?.length ?: 0 - - // prevent cursor jumping while editing a text - val cursorPosition = if (selectionEnd > textLength) textLength else selectionEnd - this.setText(text) - if (!isFocused || textLength == 0) return - - if (cursorPosition == 0) setSelection(textLength) else setSelection(cursorPosition) -} - -fun EditText.setOnImeActionListener(action: Int, handler: (EditText) -> Unit) { - this.setOnEditorActionListener { view, actionId, event -> - if (actionId == action) { - handler.invoke(this) - return@setOnEditorActionListener true - } - return@setOnEditorActionListener false - } -} - -fun TextSwitcher.update(text: CharSequence?) { - val textView = this.currentView as? TextView ?: return - - if (textView.text?.toString() != text?.toString()) this.setText(text) -} - -// By default the TextInputLayout didn't activates the error state if the message is empty or null -fun TextInputLayout.enableError(enable: Boolean, errorMessage: String? = null) { - if (enable) { - if (errorMessage == null || errorMessage.isEmpty()) { - error = "Any message" - if (childCount == 2) getChildAt(1).visibility = View.GONE - } else { - error = errorMessage - } - } else { - error = null - isErrorEnabled = false - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/RootAppComponentContextModule.kt b/app/src/main/java/com/tangem/tap/di/RootAppComponentContextModule.kt index b59094d579..0d535ff71d 100644 --- a/app/src/main/java/com/tangem/tap/di/RootAppComponentContextModule.kt +++ b/app/src/main/java/com/tangem/tap/di/RootAppComponentContextModule.kt @@ -5,7 +5,7 @@ import androidx.appcompat.app.AppCompatActivity import com.arkivanov.decompose.defaultComponentContext import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.DefaultAppComponentContext -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.di.GlobalUiMessageSender import com.tangem.core.decompose.di.RootAppComponentContext import com.tangem.core.decompose.ui.UiMessageSender @@ -27,7 +27,7 @@ internal object RootAppComponentContextModule { fun provideRootAppComponentContext( @ActivityContext context: Context, dispatchers: CoroutineDispatcherProvider, - componentBuilder: DecomposeComponent.Builder, + componentBuilder: ModelComponent.Builder, @GlobalUiMessageSender messageSender: UiMessageSender, ): AppComponentContext { return DefaultAppComponentContext( diff --git a/app/src/main/java/com/tangem/tap/di/domain/AnalyticsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/AnalyticsDomainModule.kt index dda9f2c253..8e89c073b8 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/AnalyticsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/AnalyticsDomainModule.kt @@ -5,10 +5,10 @@ import com.tangem.domain.analytics.repository.AnalyticsRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.components.ViewModelComponent +import dagger.hilt.components.SingletonComponent @Module -@InstallIn(ViewModelComponent::class) +@InstallIn(SingletonComponent::class) internal object AnalyticsDomainModule { @Provides diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt index 7f5f095079..dffd82ec11 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt @@ -7,6 +7,8 @@ import com.tangem.blockchain.common.Wallet import com.tangem.common.CompletionResult import com.tangem.domain.card.models.TwinKey import com.tangem.domain.models.scan.isRing +import com.tangem.operations.sign.SignData +import com.tangem.tap.domain.tasks.MultiSignHashTask import com.tangem.tap.domain.tasks.SignHashesTask import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume @@ -64,6 +66,40 @@ class TangemSigner( is CompletionResult.Failure -> CompletionResult.Failure(result.error) } } + + override suspend fun multiSign( + dataToSign: List, + publicKey: Wallet.PublicKey, + ): CompletionResult> { + return suspendCancellableCoroutine { continuation -> + val task = MultiSignHashTask(dataToSign, publicKey, twinKey?.getPairKey(publicKey.seedKey)) + + tangemSdk.startSessionWithRunnable( + runnable = task, + cardId = cardId, + initialMessage = initialMessage, + ) { result -> + when (result) { + is CompletionResult.Success -> { + signerCallback( + TangemSignerResponse( + totalSignedHashes = result.data.totalSignedHashes, + remainingSignatures = result.data.remainingSignatures, + isRing = result.data.batchId?.let(::isRing) ?: false, + ), + ) + if (continuation.isActive) { + continuation.resume(CompletionResult.Success(result.data.signatures)) + } + } + is CompletionResult.Failure -> + if (continuation.isActive) { + continuation.resume(CompletionResult.Failure(result.error)) + } + } + } + } + } } data class TangemSignerResponse( diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt index d0f4cc5985..abf3ce3f3c 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt @@ -490,6 +490,7 @@ internal class DefaultTangemSdkManager( activationInput = activationInput, coroutineScope = this, ), + cardId = activationInput.cardId, initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)), ) } @@ -502,6 +503,7 @@ internal class DefaultTangemSdkManager( runnable = VisaCustomerWalletApproveTask( visaDataForApprove = visaDataForApprove, ), + cardId = visaDataForApprove.customerWalletCardId, initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)), ) } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/MultiSignHashTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/MultiSignHashTask.kt new file mode 100644 index 0000000000..d1d0d3dc53 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/MultiSignHashTask.kt @@ -0,0 +1,80 @@ +package com.tangem.tap.domain.tasks + +import com.tangem.blockchain.common.Wallet +import com.tangem.common.CompletionResult +import com.tangem.common.core.CardSession +import com.tangem.common.core.CardSessionRunnable +import com.tangem.common.core.CompletionCallback +import com.tangem.common.core.TangemSdkError +import com.tangem.operations.CommandResponse +import com.tangem.operations.sign.MultipleSignCommand +import com.tangem.operations.sign.SignData + +class TangemMultiSignHashResponse( + val signatures: Map, + val totalSignedHashes: Int?, + val remainingSignatures: Int?, + val batchId: String?, +) : CommandResponse + +class MultiSignHashTask( + private val dataToSign: List, + private val publicKey: Wallet.PublicKey, + private val pairWalletPublicKey: ByteArray?, +) : CardSessionRunnable { + + override fun run(session: CardSession, callback: CompletionCallback) { + MultipleSignCommand(dataToSign, publicKey.seedKey).run(session) { response -> + when (response) { + is CompletionResult.Success -> { + val card = session.environment.card + callback( + CompletionResult.Success( + TangemMultiSignHashResponse( + signatures = response.data.associate { it.walletPublicKey to it.signature }, + totalSignedHashes = response.data.last().totalSignedHashes, + remainingSignatures = card?.wallet(publicKey.seedKey)?.remainingSignatures, + batchId = card?.batchId, + ), + ), + ) + } + is CompletionResult.Failure -> { + when { + response.error is TangemSdkError.WalletNotFound && pairWalletPublicKey != null -> { + sign(session, pairWalletPublicKey, callback) + } + else -> callback(CompletionResult.Failure(response.error)) + } + } + } + } + } + + private fun sign( + session: CardSession, + publicKey: ByteArray, + callback: CompletionCallback, + ) { + MultipleSignCommand(dataToSign, publicKey).run(session) { response -> + when (response) { + is CompletionResult.Success -> { + val card = session.environment.card + callback( + CompletionResult.Success( + TangemMultiSignHashResponse( + signatures = response.data.associate { it.walletPublicKey to it.signature }, + totalSignedHashes = response.data.last().totalSignedHashes, + remainingSignatures = card?.wallet(publicKey)?.remainingSignatures, + batchId = card?.batchId, + ), + ), + ) + } + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(response.error)) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt index 2697b453b7..e7a9f1873f 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt @@ -4,12 +4,15 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.visa.model.VisaCardActivationStatus import com.tangem.domain.wallets.models.UserWalletId @JsonClass(generateAdapter = true) internal data class UserWalletSensitiveInformation( @Json(name = "wallets") val wallets: List, + @Json(name = "visaCardActivationStatus") + val visaCardActivationStatus: VisaCardActivationStatus? = null, ) @JsonClass(generateAdapter = true) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt index 7b869eb69f..4c73101416 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt @@ -6,7 +6,10 @@ import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation internal val UserWallet.sensitiveInformation: UserWalletSensitiveInformation - get() = UserWalletSensitiveInformation(scanResponse.card.wallets) + get() = UserWalletSensitiveInformation( + wallets = scanResponse.card.wallets, + visaCardActivationStatus = scanResponse.visaCardActivationStatus, + ) internal val UserWallet.publicInformation: UserWalletPublicInformation get() = UserWalletPublicInformation( @@ -19,6 +22,7 @@ internal val UserWallet.publicInformation: UserWalletPublicInformation card = scanResponse.card.copy( wallets = emptyList(), ), + visaCardActivationStatus = null, ), hasBackupError = hasBackupError, ) @@ -45,6 +49,7 @@ internal fun UserWallet.updateWith(sensitiveInformation: UserWalletSensitiveInfo card = scanResponse.card.copy( wallets = sensitiveInformation.wallets, ), + visaCardActivationStatus = sensitiveInformation.visaCardActivationStatus, ), ) } @@ -70,5 +75,6 @@ internal fun UserWallet.lock(): UserWallet = copy( card = scanResponse.card.copy( wallets = emptyList(), ), + visaCardActivationStatus = null, ), ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt index d49c3386d7..27eb47df0b 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt @@ -9,11 +9,11 @@ import com.reown.walletkit.client.Wallet import com.reown.walletkit.client.WalletKit import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.tap.common.analytics.events.WalletConnect -import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository -import com.tangem.tap.domain.walletconnect2.domain.WcJrpcMethods -import com.tangem.tap.domain.walletconnect2.domain.WcJrpcRequestsDeserializer -import com.tangem.tap.domain.walletconnect2.domain.WcRequest +import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper +import com.tangem.tap.domain.walletconnect2.domain.* import com.tangem.tap.domain.walletconnect2.domain.models.* +import com.tangem.tap.domain.walletconnect2.toggles.WalletConnectFeatureToggles +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -24,6 +24,7 @@ internal class DefaultLegacyWalletConnectRepository( private val application: Application, private val wcRequestDeserializer: WcJrpcRequestsDeserializer, private val analyticsHandler: AnalyticsEventHandler, + private val walletConnectFeatureToggles: WalletConnectFeatureToggles, ) : LegacyWalletConnectRepository { private var sessionProposal: Wallet.Model.SessionProposal? = null @@ -35,6 +36,7 @@ internal class DefaultLegacyWalletConnectRepository( private val _activeSessions: MutableSharedFlow> = MutableSharedFlow() override val activeSessions: Flow> = _activeSessions + private val blockchainHelper by lazy { TangemWcBlockchainHelper(walletConnectFeatureToggles) } override var currentSessions: List = emptyList() private set @@ -136,6 +138,13 @@ internal class DefaultLegacyWalletConnectRepository( userNamespaces = this@DefaultLegacyWalletConnectRepository.userNamespaces ?: emptyMap(), ) + val requiredChainIds = sessionProposal.requiredNamespaces.values.flatMap { it.chains ?: emptyList() } + val optionalChainIds = optionalWithoutMissingNetworks.toList() + val networks = (requiredChainIds + optionalChainIds) + .mapNotNull { blockchainHelper.chainIdToFullNameOrNull(it) } + .distinct() + analyticsHandler.send(WalletConnect.DAppConnectionRequested(networks)) + scope.launch { _events.emit( WalletConnectEvents.SessionProposal( @@ -143,8 +152,8 @@ internal class DefaultLegacyWalletConnectRepository( sessionProposal.description, sessionProposal.url, sessionProposal.icons, - sessionProposal.requiredNamespaces.values.flatMap { it.chains ?: emptyList() }, - optionalWithoutMissingNetworks.toList(), + requiredChainIds, + optionalChainIds, ), ) } @@ -176,7 +185,17 @@ internal class DefaultLegacyWalletConnectRepository( result = "", ) } - else -> + else -> { + val event = WalletConnect.SignatureRequestReceived( + WalletConnect.RequestHandledParams( + dAppName = sessionRequest.peerMetaData?.name ?: "", + dAppUrl = sessionRequest.peerMetaData?.url ?: "", + methodName = sessionRequest.request.method, + blockchain = sessionRequest.chainId + ?.let { blockchainHelper.chainIdToNetworkIdOrNull(it) } ?: "", + ), + ) + analyticsHandler.send(event) scope.launch { _events.emit( WalletConnectEvents.SessionRequest( @@ -190,6 +209,7 @@ internal class DefaultLegacyWalletConnectRepository( ), ) } + } } } @@ -247,7 +267,8 @@ internal class DefaultLegacyWalletConnectRepository( this.userNamespaces = userNamespaces } - override fun pair(uri: String) { + override fun pair(uri: String, source: SourceType) { + analyticsHandler.send(WalletConnect.NewSessionInitiated(source = source)) WalletKit.pair( params = Wallet.Params.Pair(uri), onSuccess = { @@ -255,6 +276,7 @@ internal class DefaultLegacyWalletConnectRepository( }, onError = { Timber.e("Error while pairing: $it") + analyticsHandler.send(WalletConnect.SessionFailed) scope.launch { _events.emit( WalletConnectEvents.PairConnectError(it.throwable), @@ -305,7 +327,7 @@ internal class DefaultLegacyWalletConnectRepository( onSuccess = { Timber.i("Approved successfully: $it") analyticsHandler.send( - WalletConnect.NewSessionEstablished( + WalletConnect.DAppConnected( dAppName = sessionProposal.name, dAppUrl = sessionProposal.url, blockchainNames = blockchainNames, @@ -314,6 +336,13 @@ internal class DefaultLegacyWalletConnectRepository( }, onError = { Timber.e("Error while approving: $it") + analyticsHandler.send( + WalletConnect.DAppConnectionFailed( + dAppName = sessionProposal.name, + dAppUrl = sessionProposal.url, + blockchainNames = blockchainNames, + ), + ) scope.launch { _events.emit( WalletConnectEvents.SessionApprovalError( @@ -352,7 +381,7 @@ internal class DefaultLegacyWalletConnectRepository( // Add Ethereum Chain method is processed without user input, skip logging it if (requestData.method != WcJrpcMethods.WALLET_ADD_ETHEREUM_CHAIN.code) { analyticsHandler.send( - WalletConnect.RequestHandled( + WalletConnect.SignatureRequestHandled( WalletConnect.RequestHandledParams( dAppName = session?.name ?: "", dAppUrl = session?.url ?: "", @@ -377,7 +406,7 @@ internal class DefaultLegacyWalletConnectRepository( onError = { error -> Timber.e(error.throwable, "Error while responging session request") - WalletConnect.RequestHandledParams( + val params = WalletConnect.RequestHandledParams( dAppName = session?.name ?: "", dAppUrl = session?.url ?: "", methodName = requestData.method, @@ -385,6 +414,7 @@ internal class DefaultLegacyWalletConnectRepository( errorCode = WalletConnectError.ValidationError.error, errorDescription = error.throwable.message, ) + analyticsHandler.send(WalletConnect.SignatureRequestFailed(params)) }, ) } @@ -393,7 +423,7 @@ internal class DefaultLegacyWalletConnectRepository( val session = currentSessions.find { it.topic == requestData.topic } analyticsHandler.send( - WalletConnect.RequestHandled( + WalletConnect.SignatureRequestHandled( WalletConnect.RequestHandledParams( dAppName = session?.name ?: "", dAppUrl = session?.url ?: "", diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt index 9b7a44b10d..80319c8ce3 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt @@ -14,9 +14,7 @@ import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper import com.tangem.tap.domain.walletconnect2.app.WalletConnectEventsHandlerImpl import com.tangem.tap.domain.walletconnect2.data.DefaultLegacyWalletConnectRepository import com.tangem.tap.domain.walletconnect2.data.DefaultWalletConnectSessionsRepository -import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository -import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor -import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository +import com.tangem.tap.domain.walletconnect2.domain.* import com.tangem.tap.domain.walletconnect2.domain.WcJrpcRequestsDeserializer import com.tangem.tap.domain.walletconnect2.toggles.WalletConnectFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -71,11 +69,13 @@ internal object WalletConnectModule { application: Application, wcRequestDeserializer: WcJrpcRequestsDeserializer, analyticsHandler: AnalyticsEventHandler, + walletConnectFeatureToggles: WalletConnectFeatureToggles, ): LegacyWalletConnectRepository { return DefaultLegacyWalletConnectRepository( application = application, wcRequestDeserializer = wcRequestDeserializer, analyticsHandler = analyticsHandler, + walletConnectFeatureToggles = walletConnectFeatureToggles, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt index d755da5aea..289a2664b5 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt @@ -1,6 +1,7 @@ package com.tangem.tap.domain.walletconnect2.domain import com.tangem.tap.domain.walletconnect2.domain.models.* +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType import kotlinx.coroutines.flow.Flow interface LegacyWalletConnectRepository { @@ -17,7 +18,7 @@ interface LegacyWalletConnectRepository { fun updateSessions() - fun pair(uri: String) + fun pair(uri: String, source: SourceType) fun disconnect(topic: String) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index 48f90f3554..be7ff95089 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -81,7 +81,7 @@ class WalletConnectInteractor( } } - private suspend fun initWithWallet(userWallet: UserWallet) { + private fun initWithWallet(userWallet: UserWallet) { if (userWallet.isMultiCurrency) { Timber.i("WalletConnect: initialize and setup networks for ${userWallet.walletId}") startListeningWc(userWallet.walletId.stringValue, getCardId(userWallet)) @@ -109,7 +109,7 @@ class WalletConnectInteractor( handleDeeplinkStack(accounts) } - private suspend fun startListeningWc(userWalletId: String, cardId: String?) { + private fun startListeningWc(userWalletId: String, cardId: String?) { this.userWalletId = userWalletId this.cardId = cardId listenerScope.coroutineContext.cancelChildren() @@ -135,7 +135,9 @@ class WalletConnectInteractor( isWalletConnectReadyForDeepLinks = true if (deeplinkStack.empty()) return val lastDeeplink = deeplinkStack.pop() - store.dispatchOnMain(WalletConnectAction.OpenSession(lastDeeplink)) + val action = WalletConnectAction + .OpenSession(lastDeeplink, WalletConnectAction.OpenSession.SourceType.DEEPLINK) + store.dispatchOnMain(action) }.onFailure { Timber.e("WC deeplink handling failed. $it") } @@ -147,18 +149,21 @@ class WalletConnectInteractor( Timber.i("WalletConnect: event: $wcEvent") when (wcEvent) { is WalletConnectEvents.SessionProposal -> { - val unsupportedNetworks = wcEvent.requiredChainIds - .filter { blockchainHelper.chainIdToNetworkIdOrNull(it) == null } - if (unsupportedNetworks.isNotEmpty()) { + val unsupportedNetworks = wcEvent.requiredChainIds.filter { + blockchainHelper.chainIdToNetworkIdOrNull(it) == null + } + + val supportedNetworks = (wcEvent.requiredChainIds + wcEvent.optionalChainIds) + .mapNotNull(blockchainHelper::chainIdToFullNameOrNull) + .distinct() + + if (unsupportedNetworks.isNotEmpty() || supportedNetworks.isEmpty()) { val error = WalletConnectError.ApprovalErrorUnsupportedNetwork(unsupportedNetworks) handler.onSessionRejected(error) return@onEach } - val networksFormatted = (wcEvent.requiredChainIds + wcEvent.optionalChainIds) - .mapNotNull { blockchainHelper.chainIdToFullNameOrNull(it) } - .distinct() - .toString() - handler.onProposalReceived(proposal = wcEvent, networksFormatted = networksFormatted) + + handler.onProposalReceived(proposal = wcEvent, networksFormatted = supportedNetworks.toString()) } is WalletConnectEvents.SessionApprovalError -> { val error = when (wcEvent.error) { @@ -392,7 +397,8 @@ class WalletConnectInteractor( } if (isWalletConnectReadyForDeepLinks) { - store.dispatchOnMain(WalletConnectAction.OpenSession(deeplink)) + val action = WalletConnectAction.OpenSession(deeplink, WalletConnectAction.OpenSession.SourceType.DEEPLINK) + store.dispatchOnMain(action) } else { deeplinkStack.push(deeplink) } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index 53844799aa..6b648b7c0d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -1,9 +1,9 @@ package com.tangem.tap.features.details.redux -import androidx.lifecycle.LifecycleCoroutineScope import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.models.scan.ScanResponse +import kotlinx.coroutines.CoroutineScope import org.rekotlin.Action sealed class DetailsAction : Action { @@ -27,7 +27,7 @@ sealed class DetailsAction : Action { } data class CheckBiometricsStatus( - val lifecycleScope: LifecycleCoroutineScope, + val coroutineScope: CoroutineScope, ) : AppSettings() data object EnrollBiometrics : AppSettings() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 106bc50e1e..af4c05b261 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -1,6 +1,5 @@ package com.tangem.tap.features.details.redux -import androidx.lifecycle.LifecycleCoroutineScope import com.tangem.common.CompletionResult import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess @@ -22,6 +21,7 @@ import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow @@ -67,7 +67,7 @@ class DetailsMiddleware { } } is DetailsAction.AppSettings.CheckBiometricsStatus -> { - observeBiometricsStatusChanges(action.lifecycleScope) + observeBiometricsStatusChanges(action.coroutineScope) } is DetailsAction.AppSettings.EnrollBiometrics -> { enrollBiometrics() @@ -90,7 +90,7 @@ class DetailsMiddleware { } } - private fun observeBiometricsStatusChanges(lifecycleScope: LifecycleCoroutineScope) { + private fun observeBiometricsStatusChanges(scope: CoroutineScope) { val needEnrollBiometricsFlow = flow { do { val needEnrollBiometrics = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() @@ -108,7 +108,7 @@ class DetailsMiddleware { .onEach { needEnrollBiometrics -> store.dispatchWithMain(DetailsAction.AppSettings.BiometricsStatusChanged(needEnrollBiometrics)) } - .launchIn(lifecycleScope) + .launchIn(scope) .saveIn(checkBiometricsStatusJobHolder) } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt index 476bcd288d..3383203b41 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt @@ -15,7 +15,10 @@ sealed class WalletConnectAction : Action { data class OpenSession( val wcUri: String, - ) : WalletConnectAction() + val source: SourceType, + ) : WalletConnectAction() { + enum class SourceType { QR, DEEPLINK, ETC } + } data class DisconnectSession(val topic: String) : WalletConnectAction() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 4e7c5c7be2..8535bc008f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -76,7 +76,7 @@ class WalletConnectMiddleware { is WalletConnectAction.OpenSession -> { val index = action.wcUri.indexOf("@") when (action.wcUri[index + 1]) { - '2' -> walletConnectRepository.pair(uri = action.wcUri) + '2' -> walletConnectRepository.pair(uri = action.wcUri, source = action.source) '1' -> { store.dispatchOnMain(WalletConnectAction.UnsupportedDappRequest) store.dispatchOnMain( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorFragment.kt deleted file mode 100644 index 1b64c97cf2..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorFragment.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.tangem.tap.features.details.ui.appcurrency - -import androidx.activity.compose.BackHandler -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.fragment.app.viewModels -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.tangem.common.routing.AppRouter -import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.components.NavigationBar3ButtonsScrim -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.store -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -@AndroidEntryPoint -internal class AppCurrencySelectorFragment : ComposeFragment() { - - @Inject - override lateinit var uiDependencies: UiDependencies - - private val viewModel: AppCurrencySelectorViewModel by viewModels() - - @Composable - override fun ScreenContent(modifier: Modifier) { - BackHandler { store.dispatchNavigationAction(AppRouter::pop) } - - val uiState by viewModel.uiState.collectAsStateWithLifecycle() - NavigationBar3ButtonsScrim() - AppCurrencySelectorScreen( - modifier = modifier, - state = uiState, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/DefaultAppCurrencySelectorComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/DefaultAppCurrencySelectorComponent.kt new file mode 100644 index 0000000000..6f6ec872a4 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/DefaultAppCurrencySelectorComponent.kt @@ -0,0 +1,38 @@ +package com.tangem.tap.features.details.ui.appcurrency + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.components.NavigationBar3ButtonsScrim +import com.tangem.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent +import com.tangem.tap.features.details.ui.appcurrency.model.AppCurrencySelectorModel +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +@Suppress("UnusedPrivateMember") +internal class DefaultAppCurrencySelectorComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: Unit, +) : AppCurrencySelectorComponent, AppComponentContext by appComponentContext { + + private val model: AppCurrencySelectorModel = getOrCreateModel() + + @Composable + override fun Content(modifier: Modifier) { + val uiState by model.uiState.collectAsStateWithLifecycle() + NavigationBar3ButtonsScrim() + AppCurrencySelectorScreen( + modifier = modifier, + state = uiState, + ) + } + + @AssistedFactory + interface Factory : AppCurrencySelectorComponent.Factory { + override fun create(context: AppComponentContext, params: Unit): DefaultAppCurrencySelectorComponent + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/api/AppCurrencySelectorComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/api/AppCurrencySelectorComponent.kt new file mode 100644 index 0000000000..3b823712b2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/api/AppCurrencySelectorComponent.kt @@ -0,0 +1,9 @@ +package com.tangem.tap.features.details.ui.appcurrency.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent + +interface AppCurrencySelectorComponent : ComposableContentComponent { + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/di/AppCurrencySelectorFeatureModule.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/di/AppCurrencySelectorFeatureModule.kt new file mode 100644 index 0000000000..293c87d7a3 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/di/AppCurrencySelectorFeatureModule.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.features.details.ui.appcurrency.di + +import com.tangem.core.decompose.model.Model +import com.tangem.tap.features.details.ui.appcurrency.DefaultAppCurrencySelectorComponent +import com.tangem.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent +import com.tangem.tap.features.details.ui.appcurrency.model.AppCurrencySelectorModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface AppCurrencySelectorFeatureModule { + + @Binds + fun bindFactory(impl: DefaultAppCurrencySelectorComponent.Factory): AppCurrencySelectorComponent.Factory + + @Binds + @IntoMap + @ClassKey(AppCurrencySelectorModel::class) + fun bindModel(model: AppCurrencySelectorModel): Model +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/model/AppCurrencySelectorModel.kt similarity index 77% rename from app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorViewModel.kt rename to app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/model/AppCurrencySelectorModel.kt index 41d27d02a9..f48804c41c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/model/AppCurrencySelectorModel.kt @@ -1,35 +1,38 @@ -package com.tangem.tap.features.details.ui.appcurrency +package com.tangem.tap.features.details.ui.appcurrency.model -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.compose.runtime.Stable import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.api.AnalyticsEventHandler - +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model import com.tangem.domain.appcurrency.GetAvailableCurrenciesUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.SelectAppCurrencyUseCase import com.tangem.tap.common.analytics.events.Settings +import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorIntents +import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorState +import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorStateHolder import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import javax.inject.Inject -@HiltViewModel -internal class AppCurrencySelectorViewModel @Inject constructor( +@Stable +@ModelScoped +internal class AppCurrencySelectorModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getAvailableCurrenciesUseCase: GetAvailableCurrenciesUseCase, private val selectAppCurrencyUseCase: SelectAppCurrencyUseCase, private val router: AppRouter, - private val dispatchers: CoroutineDispatcherProvider, private val analyticsEventHandler: AnalyticsEventHandler, -) : ViewModel(), AppCurrencySelectorIntents { +) : Model(), AppCurrencySelectorIntents { private val stateController = AppCurrencySelectorStateHolder( intents = this, onSubscription = { fetchCurrencies() }, - stateFlowScope = viewModelScope, + stateFlowScope = modelScope, ) val uiState: StateFlow = stateController.stateFlow @@ -47,7 +50,7 @@ internal class AppCurrencySelectorViewModel @Inject constructor( } override fun onCurrencyClick(currency: AppCurrencySelectorState.Currency) { - viewModelScope.launch(dispatchers.io) { + modelScope.launch(dispatchers.io) { selectAppCurrencyUseCase(currency.id) .onRight { analyticsEventHandler.send( @@ -63,7 +66,7 @@ internal class AppCurrencySelectorViewModel @Inject constructor( } private fun fetchCurrencies() { - viewModelScope.launch(dispatchers.io) { + modelScope.launch(dispatchers.io) { val availableCurrencies = getAvailableCurrenciesUseCase() .onRight(stateController::updateStateWithAvailableCurrencies) .getOrNull() diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt deleted file mode 100644 index b74a994acc..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.tangem.tap.features.details.ui.appsettings - -import androidx.activity.compose.BackHandler -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.tangem.common.routing.AppRouter -import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.domain.appcurrency.repository.AppCurrencyRepository -import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.store -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -@AndroidEntryPoint -internal class AppSettingsFragment : ComposeFragment() { - - @Inject - override lateinit var uiDependencies: UiDependencies - - @Inject - lateinit var appCurrencyRepository: AppCurrencyRepository - - @Composable - override fun ScreenContent(modifier: Modifier) { - BackHandler { store.dispatchNavigationAction(AppRouter::pop) } - - val viewModel = hiltViewModel().apply { - LocalLifecycleOwner.current.lifecycle.addObserver(observer = this) - } - val state by viewModel.uiState.collectAsStateWithLifecycle() - - AppSettingsScreen( - modifier = modifier, - state = state, - onBackClick = { - store.dispatchNavigationAction(AppRouter::pop) - }, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/DefaultAppSettingsComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/DefaultAppSettingsComponent.kt new file mode 100644 index 0000000000..a92b632003 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/DefaultAppSettingsComponent.kt @@ -0,0 +1,49 @@ +package com.tangem.tap.features.details.ui.appsettings + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.arkivanov.essenty.lifecycle.doOnResume +import com.tangem.common.routing.AppRouter +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.tap.common.extensions.dispatchNavigationAction +import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent +import com.tangem.tap.features.details.ui.appsettings.model.AppSettingsModel +import com.tangem.tap.store +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +@Suppress("UnusedPrivateMember") +internal class DefaultAppSettingsComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: Unit, +) : AppSettingsComponent, AppComponentContext by appComponentContext { + + private val model: AppSettingsModel = getOrCreateModel() + + init { + + doOnResume { model.onResume() } + } + + @Composable + override fun Content(modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + + AppSettingsScreen( + modifier = modifier, + state = state, + onBackClick = { + store.dispatchNavigationAction(AppRouter::pop) + }, + ) + } + + @AssistedFactory + interface Factory : AppSettingsComponent.Factory { + override fun create(context: AppComponentContext, params: Unit): DefaultAppSettingsComponent + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/analytics/AppSettingsItemsAnalyticsSender.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/analytics/AppSettingsItemsAnalyticsSender.kt index d61f054cb5..64a5832a8c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/analytics/AppSettingsItemsAnalyticsSender.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/analytics/AppSettingsItemsAnalyticsSender.kt @@ -2,13 +2,13 @@ package com.tangem.tap.features.details.ui.appsettings.analytics import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.decompose.di.ModelScoped import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState -import dagger.hilt.android.scopes.ViewModelScoped import javax.inject.Inject -@ViewModelScoped +@ModelScoped internal class AppSettingsItemsAnalyticsSender @Inject constructor( private val analyticsHandler: AnalyticsEventHandler, ) { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/api/AppSettingsComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/api/AppSettingsComponent.kt new file mode 100644 index 0000000000..62be87bc2c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/api/AppSettingsComponent.kt @@ -0,0 +1,9 @@ +package com.tangem.tap.features.details.ui.appsettings.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent + +interface AppSettingsComponent : ComposableContentComponent { + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsButtonItem.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsButtonItem.kt index 17dccc7a63..537029db99 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsButtonItem.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsButtonItem.kt @@ -5,9 +5,8 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Surface -import androidx.compose.material.Text +import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -20,7 +19,6 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Item -@OptIn(ExperimentalMaterialApi::class) @Composable internal fun SettingsButtonItem(item: Item.Button, modifier: Modifier = Modifier) { Surface( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsCardItem.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsCardItem.kt index 1e43c8f328..0303b21733 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsCardItem.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsCardItem.kt @@ -2,10 +2,9 @@ package com.tangem.tap.features.details.ui.appsettings.components import android.content.res.Configuration import androidx.compose.foundation.layout.* -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Icon -import androidx.compose.material.Surface -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -22,7 +21,6 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Item -@OptIn(ExperimentalMaterialApi::class) @Composable internal fun SettingsCardItem(item: Item.Card, modifier: Modifier = Modifier) { Surface( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt index a8476e3bd2..9ad210cbef 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt @@ -4,7 +4,7 @@ import android.content.res.Configuration import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.material.Text +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/di/AppSettingsFeatureModule.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/di/AppSettingsFeatureModule.kt new file mode 100644 index 0000000000..8991fbdc06 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/di/AppSettingsFeatureModule.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.features.details.ui.appsettings.di + +import com.tangem.core.decompose.model.Model +import com.tangem.tap.features.details.ui.appsettings.DefaultAppSettingsComponent +import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent +import com.tangem.tap.features.details.ui.appsettings.model.AppSettingsModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface AppSettingsFeatureModule { + + @Binds + fun bindFactory(impl: DefaultAppSettingsComponent.Factory): AppSettingsComponent.Factory + + @Binds + @IntoMap + @ClassKey(AppSettingsModel::class) + fun bindModel(model: AppSettingsModel): Model +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/model/AppSettingsModel.kt similarity index 92% rename from app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt rename to app/src/main/java/com/tangem/tap/features/details/ui/appsettings/model/AppSettingsModel.kt index 55d16d7d80..ee93849f31 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/model/AppSettingsModel.kt @@ -1,8 +1,10 @@ -package com.tangem.tap.features.details.ui.appsettings +package com.tangem.tap.features.details.ui.appsettings.model -import androidx.lifecycle.* +import androidx.compose.runtime.Stable import com.tangem.common.routing.AppRoute import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.apptheme.model.AppThemeMode @@ -20,12 +22,15 @@ import com.tangem.tap.features.details.redux.AppSetting import com.tangem.tap.features.details.redux.AppSettingsState import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState +import com.tangem.tap.features.details.ui.appsettings.AppSettingsDialogsFactory +import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory +import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState import com.tangem.tap.features.details.ui.appsettings.analytics.AppSettingsItemsAnalyticsSender import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.* @@ -34,8 +39,10 @@ import org.rekotlin.StoreSubscriber import javax.inject.Inject @Suppress("LongParameterList") -@HiltViewModel -internal class AppSettingsViewModel @Inject constructor( +@Stable +@ModelScoped +internal class AppSettingsModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, private val appCurrencyRepository: AppCurrencyRepository, private val walletsRepository: WalletsRepository, private val canUseBiometryUseCase: CanUseBiometryUseCase, @@ -44,9 +51,7 @@ internal class AppSettingsViewModel @Inject constructor( private val appThemeModeRepository: AppThemeModeRepository, private val settingsRepository: SettingsRepository, private val appSettingsItemsAnalyticsSender: AppSettingsItemsAnalyticsSender, -) : ViewModel(), - StoreSubscriber, - DefaultLifecycleObserver { +) : Model(), StoreSubscriber { private val itemsFactory = AppSettingsItemsFactory() private val dialogsFactory = AppSettingsDialogsFactory() @@ -82,11 +87,12 @@ internal class AppSettingsViewModel @Inject constructor( } } - override fun onResume(owner: LifecycleOwner) { - store.dispatch(DetailsAction.AppSettings.CheckBiometricsStatus(owner.lifecycleScope)) + fun onResume() { + store.dispatch(DetailsAction.AppSettings.CheckBiometricsStatus(modelScope)) } - override fun onCleared() { + override fun onDestroy() { + super.onDestroy() store.unsubscribe(subscriber = this) } @@ -225,7 +231,7 @@ internal class AppSettingsViewModel @Inject constructor( .saveIn(appCurrencyUpdatesJobHolder) } - private fun bootstrapBiometricsUpdates() = viewModelScope.launch { + private fun bootstrapBiometricsUpdates() = modelScope.launch { val state = AppSettingsState( saveWallets = walletsRepository.shouldSaveUserWalletsSync(), saveAccessCodes = settingsRepository.shouldSaveAccessCodes(), diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt deleted file mode 100644 index dad1ac2b82..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.tangem.tap.features.details.ui.cardsettings - -import androidx.activity.compose.BackHandler -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.fragment.app.viewModels -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.tangem.common.routing.AppRouter -import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.store -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -@AndroidEntryPoint -internal class CardSettingsFragment : ComposeFragment() { - - @Inject - override lateinit var uiDependencies: UiDependencies - - private val viewModel: CardSettingsViewModel by viewModels() - - @Composable - override fun ScreenContent(modifier: Modifier) { - BackHandler { store.dispatchNavigationAction(AppRouter::pop) } - - val state by viewModel.screenState.collectAsStateWithLifecycle() - - CardSettingsScreen(modifier = modifier, state = state) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt index c4454f830f..44fdad55f0 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt @@ -8,7 +8,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Text +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/DefaultCardSettingsComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/DefaultCardSettingsComponent.kt new file mode 100644 index 0000000000..bb92d1a9a1 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/DefaultCardSettingsComponent.kt @@ -0,0 +1,36 @@ +package com.tangem.tap.features.details.ui.cardsettings + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent +import com.tangem.tap.features.details.ui.cardsettings.model.CardSettingsModel +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultCardSettingsComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: CardSettingsComponent.Params, +) : CardSettingsComponent, AppComponentContext by appComponentContext { + + private val model: CardSettingsModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.screenState.collectAsStateWithLifecycle() + + CardSettingsScreen(modifier = modifier, state = state) + } + + @AssistedFactory + interface Factory : CardSettingsComponent.Factory { + override fun create( + context: AppComponentContext, + params: CardSettingsComponent.Params, + ): DefaultCardSettingsComponent + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/api/CardSettingsComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/api/CardSettingsComponent.kt new file mode 100644 index 0000000000..608362caa8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/api/CardSettingsComponent.kt @@ -0,0 +1,14 @@ +package com.tangem.tap.features.details.ui.cardsettings.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.wallets.models.UserWalletId + +interface CardSettingsComponent : ComposableContentComponent { + + data class Params( + val userWalletId: UserWalletId, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt deleted file mode 100644 index bcfe7cd28d..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.tangem.tap.features.details.ui.cardsettings.coderecovery - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.fragment.app.viewModels -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.tangem.common.routing.AppRouter -import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.store -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -@AndroidEntryPoint -class AccessCodeRecoveryFragment : ComposeFragment() { - - private val viewModel: AccessCodeRecoveryViewModel by viewModels() - - @Inject - override lateinit var uiDependencies: UiDependencies - - @Composable - override fun ScreenContent(modifier: Modifier) { - val state by viewModel.screenState.collectAsStateWithLifecycle() - - AccessCodeRecoveryScreen( - state = state, - onBackClick = { store.dispatchNavigationAction(AppRouter::pop) }, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/DefaultAccessCodeRecoveryComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/DefaultAccessCodeRecoveryComponent.kt new file mode 100644 index 0000000000..fd75548292 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/DefaultAccessCodeRecoveryComponent.kt @@ -0,0 +1,40 @@ +package com.tangem.tap.features.details.ui.cardsettings.coderecovery + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.common.routing.AppRouter +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.tap.common.extensions.dispatchNavigationAction +import com.tangem.tap.features.details.ui.cardsettings.coderecovery.api.AccessCodeRecoveryComponent +import com.tangem.tap.features.details.ui.cardsettings.coderecovery.model.AccessCodeRecoveryModel +import com.tangem.tap.store +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +@Suppress("UnusedPrivateMember") +internal class DefaultAccessCodeRecoveryComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: Unit, +) : AccessCodeRecoveryComponent, AppComponentContext by appComponentContext { + + private val model: AccessCodeRecoveryModel = getOrCreateModel() + + @Composable + override fun Content(modifier: Modifier) { + val state by model.screenState.collectAsStateWithLifecycle() + + AccessCodeRecoveryScreen( + state = state, + onBackClick = { store.dispatchNavigationAction(AppRouter::pop) }, + ) + } + + @AssistedFactory + interface Factory : AccessCodeRecoveryComponent.Factory { + override fun create(context: AppComponentContext, params: Unit): DefaultAccessCodeRecoveryComponent + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/api/AccessCodeRecoveryComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/api/AccessCodeRecoveryComponent.kt new file mode 100644 index 0000000000..5cd2e1449c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/api/AccessCodeRecoveryComponent.kt @@ -0,0 +1,9 @@ +package com.tangem.tap.features.details.ui.cardsettings.coderecovery.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent + +interface AccessCodeRecoveryComponent : ComposableContentComponent { + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/di/AccessCodeRecoveryFeatureModule.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/di/AccessCodeRecoveryFeatureModule.kt new file mode 100644 index 0000000000..a4c86d70a7 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/di/AccessCodeRecoveryFeatureModule.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.features.details.ui.cardsettings.coderecovery.di + +import com.tangem.core.decompose.model.Model +import com.tangem.tap.features.details.ui.cardsettings.coderecovery.DefaultAccessCodeRecoveryComponent +import com.tangem.tap.features.details.ui.cardsettings.coderecovery.api.AccessCodeRecoveryComponent +import com.tangem.tap.features.details.ui.cardsettings.coderecovery.model.AccessCodeRecoveryModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface AccessCodeRecoveryFeatureModule { + + @Binds + fun bindFactory(impl: DefaultAccessCodeRecoveryComponent.Factory): AccessCodeRecoveryComponent.Factory + + @Binds + @IntoMap + @ClassKey(AccessCodeRecoveryModel::class) + fun bindModel(model: AccessCodeRecoveryModel): Model +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/model/AccessCodeRecoveryModel.kt similarity index 84% rename from app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryViewModel.kt rename to app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/model/AccessCodeRecoveryModel.kt index 1652506ca9..2d90ffa871 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/model/AccessCodeRecoveryModel.kt @@ -1,29 +1,33 @@ -package com.tangem.tap.features.details.ui.cardsettings.coderecovery +package com.tangem.tap.features.details.ui.cardsettings.coderecovery.model -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.compose.runtime.Stable import com.tangem.common.doOnSuccess import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.Analytics +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model import com.tangem.domain.common.util.cardTypesResolver import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchNavigationAction +import com.tangem.tap.features.details.ui.cardsettings.coderecovery.AccessCodeRecoveryScreenState import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor import com.tangem.tap.features.details.ui.common.utils.isAccessCodeRecoveryEnabled import com.tangem.tap.store -import dagger.hilt.android.lifecycle.HiltViewModel +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject -@HiltViewModel -internal class AccessCodeRecoveryViewModel @Inject constructor( +@Stable +@ModelScoped +internal class AccessCodeRecoveryModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, private val tangemSdkManager: TangemSdkManager, private val cardSettingsInteractor: CardSettingsInteractor, -) : ViewModel() { +) : Model() { private val scannedScanResponse = cardSettingsInteractor.scannedScanResponse.value ?: error("Scan response is null") @@ -47,7 +51,7 @@ internal class AccessCodeRecoveryViewModel @Inject constructor( ) } - private fun saveChanges() = viewModelScope.launch { + private fun saveChanges() = modelScope.launch { val isEnabled = screenState.value.enabledSelection tangemSdkManager diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/di/CardSettingsFeatureModule.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/di/CardSettingsFeatureModule.kt new file mode 100644 index 0000000000..78f5ba6486 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/di/CardSettingsFeatureModule.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.features.details.ui.cardsettings.di + +import com.tangem.core.decompose.model.Model +import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent +import com.tangem.tap.features.details.ui.cardsettings.DefaultCardSettingsComponent +import com.tangem.tap.features.details.ui.cardsettings.model.CardSettingsModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface CardSettingsFeatureModule { + + @Binds + fun bindFactory(impl: DefaultCardSettingsComponent.Factory): CardSettingsComponent.Factory + + @Binds + @IntoMap + @ClassKey(CardSettingsModel::class) + fun bindModel(model: CardSettingsModel): Model +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt similarity index 89% rename from app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt rename to app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt index 68d354a90c..773aa32d80 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt @@ -1,16 +1,15 @@ -package com.tangem.tap.features.details.ui.cardsettings +package com.tangem.tap.features.details.ui.cardsettings.model -import android.os.Bundle -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.compose.runtime.Stable import arrow.core.getOrElse import com.tangem.common.CompletionResult import com.tangem.common.doOnSuccess import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter -import com.tangem.common.routing.bundle.unbundle import com.tangem.core.analytics.Analytics +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.common.CardTypesResolver @@ -20,7 +19,6 @@ import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.wallets.builder.UserWalletIdBuilder -import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -29,13 +27,16 @@ import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.domain.extensions.signedHashesCount +import com.tangem.tap.features.details.ui.cardsettings.CardInfo +import com.tangem.tap.features.details.ui.cardsettings.CardSettingsScreenState +import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor import com.tangem.tap.features.details.ui.common.utils.* import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.store +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.wallet.R -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -43,22 +44,24 @@ import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList") -@HiltViewModel -internal class CardSettingsViewModel @Inject constructor( +@Stable +@ModelScoped +internal class CardSettingsModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, private val scanCardProcessor: ScanCardProcessor, private val tangemSdkManager: TangemSdkManager, private val cardSettingsInteractor: CardSettingsInteractor, private val getUserWalletUseCase: GetUserWalletUseCase, private val cardSdkConfigRepository: CardSdkConfigRepository, private val settingsRepository: SettingsRepository, - savedStateHandle: SavedStateHandle, -) : ViewModel() { +) : Model() { + + private val params = paramsContainer.require() private var previousBiometricsRequestPolicy: Boolean = false - private val userWalletId = savedStateHandle.get(AppRoute.CardSettings.USER_WALLET_ID_KEY) - ?.unbundle(UserWalletId.serializer()) - ?: error("User wallet ID is required for CardSettingsViewModel") + private val userWalletId = params.userWalletId val screenState: MutableStateFlow = MutableStateFlow(getInitialState()) @@ -68,10 +71,11 @@ internal class CardSettingsViewModel @Inject constructor( cardSettingsInteractor.scannedScanResponse .filterNotNull() .onEach(::updateCardDetails) - .launchIn(viewModelScope) + .launchIn(modelScope) } - override fun onCleared() { + override fun onDestroy() { + super.onDestroy() // Restore the previous value of access code request policy cardSdkConfigRepository.isBiometricsRequestPolicy = previousBiometricsRequestPolicy } @@ -96,7 +100,7 @@ internal class CardSettingsViewModel @Inject constructor( onBackClick = ::onBackClick, ) - private fun scanCard() = viewModelScope.launch { + private fun scanCard() = modelScope.launch { scanCardProcessor.scan( analyticsSource = com.tangem.core.analytics.models.AnalyticsParam.ScreensSources.Settings, allowsRequestAccessCodeFromRepository = true, @@ -217,7 +221,7 @@ internal class CardSettingsViewModel @Inject constructor( } } - private fun changeAccessCode() = viewModelScope.launch { + private fun changeAccessCode() = modelScope.launch { val scanResponse = requireNotNull(cardSettingsInteractor.scannedScanResponse.value) { "Scan response is null" } when (val result = tangemSdkManager.setAccessCode(scanResponse.card.cardId)) { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/DefaultResetCardComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/DefaultResetCardComponent.kt new file mode 100644 index 0000000000..16000cb7bb --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/DefaultResetCardComponent.kt @@ -0,0 +1,40 @@ +package com.tangem.tap.features.details.ui.resetcard + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.common.routing.AppRouter +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.tap.common.extensions.dispatchNavigationAction +import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent +import com.tangem.tap.features.details.ui.resetcard.model.ResetCardModel +import com.tangem.tap.store +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultResetCardComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: ResetCardComponent.Params, +) : ResetCardComponent, AppComponentContext by appComponentContext { + + private val model: ResetCardModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.screenState.collectAsStateWithLifecycle() + + ResetCardScreen( + modifier = modifier, + state = state, + onBackClick = { store.dispatchNavigationAction(AppRouter::pop) }, + ) + } + + @AssistedFactory + interface Factory : ResetCardComponent.Factory { + override fun create(context: AppComponentContext, params: ResetCardComponent.Params): DefaultResetCardComponent + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt deleted file mode 100644 index 041a91ee08..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.tap.features.details.ui.resetcard - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.fragment.app.viewModels -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.tangem.common.routing.AppRouter -import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.store -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -@AndroidEntryPoint -internal class ResetCardFragment : ComposeFragment() { - - @Inject - override lateinit var uiDependencies: UiDependencies - - private val viewModel: ResetCardViewModel by viewModels() - - @Composable - override fun ScreenContent(modifier: Modifier) { - val state by viewModel.screenState.collectAsStateWithLifecycle() - - ResetCardScreen( - state = state, - onBackClick = { store.dispatchNavigationAction(AppRouter::pop) }, - modifier = modifier, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/api/ResetCardComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/api/ResetCardComponent.kt new file mode 100644 index 0000000000..abbe76595b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/api/ResetCardComponent.kt @@ -0,0 +1,17 @@ +package com.tangem.tap.features.details.ui.resetcard.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.wallets.models.UserWalletId + +interface ResetCardComponent : ComposableContentComponent { + + data class Params( + val userWalletId: UserWalletId, + val cardId: String, + val isActiveBackupStatus: Boolean, + val backupCardsCount: Int, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/di/ResetCardFeatureModule.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/di/ResetCardFeatureModule.kt new file mode 100644 index 0000000000..cdda8d97d4 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/di/ResetCardFeatureModule.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.features.details.ui.resetcard.di + +import com.tangem.core.decompose.model.Model +import com.tangem.tap.features.details.ui.resetcard.DefaultResetCardComponent +import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent +import com.tangem.tap.features.details.ui.resetcard.model.ResetCardModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface ResetCardFeatureModule { + + @Binds + fun bindFactory(impl: DefaultResetCardComponent.Factory): ResetCardComponent.Factory + + @Binds + @IntoMap + @ClassKey(ResetCardModel::class) + fun bindModel(model: ResetCardModel): Model +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt similarity index 88% rename from app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt rename to app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt index ebcdf702a9..f401bbfc09 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt @@ -1,21 +1,19 @@ -package com.tangem.tap.features.details.ui.resetcard +package com.tangem.tap.features.details.ui.resetcard.model -import android.os.Bundle -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.compose.runtime.Stable import arrow.core.getOrElse import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.bundle.unbundle import com.tangem.common.routing.utils.popTo import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.card.DeleteSavedAccessCodesUseCase import com.tangem.domain.card.ResetCardUseCase import com.tangem.domain.card.ResetCardUserCodeParams import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.asLockable -import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.DeleteWalletUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase @@ -24,9 +22,12 @@ import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor import com.tangem.tap.features.details.ui.common.utils.getResetToFactoryDescription +import com.tangem.tap.features.details.ui.resetcard.ResetCardDialog +import com.tangem.tap.features.details.ui.resetcard.ResetCardScreenState +import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent import com.tangem.tap.store +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update @@ -35,8 +36,11 @@ import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList") -@HiltViewModel -internal class ResetCardViewModel @Inject constructor( +@Stable +@ModelScoped +internal class ResetCardModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + paramsContainer: ParamsContainer, getUserWalletUseCase: GetUserWalletUseCase, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val resetCardUseCase: ResetCardUseCase, @@ -45,13 +49,12 @@ internal class ResetCardViewModel @Inject constructor( private val userWalletsListManager: UserWalletsListManager, private val analyticsEventHandler: AnalyticsEventHandler, private val cardSettingsInteractor: CardSettingsInteractor, - savedStateHandle: SavedStateHandle, -) : ViewModel() { +) : Model() { + + private val params = paramsContainer.require() // region Card-set specific data. All cards from single set have the same userWalletId and cardTypesResolver - private val currentUserWalletId = savedStateHandle.get(AppRoute.ResetToFactory.USER_WALLET_ID) - ?.unbundle(UserWalletId.serializer()) - ?: error("UserWalletId must be provided for ResetCardViewModel") + private val currentUserWalletId = params.userWalletId // Use only for card-specific data private val userWallet = getUserWalletUseCase(userWalletId = currentUserWalletId) @@ -65,15 +68,11 @@ internal class ResetCardViewModel @Inject constructor( // endregion // region Data of card that was scanned on CardSettings - private val primaryCardId: String = savedStateHandle.get(AppRoute.ResetToFactory.CARD_ID) - ?: error("CardId must be provided for ResetCardViewModel") + private val primaryCardId: String = params.cardId - private val isActiveBackupPrimaryCard = - savedStateHandle.get(AppRoute.ResetToFactory.IS_ACTIVE_BACKUP_STATUS) - ?: error("IsActiveBackupCard must be provided for ResetCardViewModel") + private val isActiveBackupPrimaryCard = params.isActiveBackupStatus - private val primaryBackupCardsCount = savedStateHandle.get(AppRoute.ResetToFactory.BACKUP_CARDS_COUNT) - ?: error("CardCount must be provided for ResetCardViewModel") + private val primaryBackupCardsCount = params.backupCardsCount // endregion // TODO: move logic to separate domain entity @@ -177,7 +176,7 @@ internal class ResetCardViewModel @Inject constructor( } private fun makeFullReset() { - viewModelScope.launch { + modelScope.launch { resetCardUseCase(cardId = primaryCardId, params = currentUserCodeParams).onRight { deleteSavedAccessCodesUseCase(cardId = primaryCardId) val hasUserWallets = deleteWalletUseCase(userWalletId = currentUserWalletId).getOrElse { @@ -203,7 +202,7 @@ internal class ResetCardViewModel @Inject constructor( private fun onContinueResetClick() { dismissDialog() - viewModelScope.launch { + modelScope.launch { resetCardUseCase( cardNumber = resetBackupCardCount + 1, params = currentUserCodeParams, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/DefaultSecurityModeComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/DefaultSecurityModeComponent.kt new file mode 100644 index 0000000000..151b4c123d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/DefaultSecurityModeComponent.kt @@ -0,0 +1,43 @@ +package com.tangem.tap.features.details.ui.securitymode + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.common.routing.AppRouter +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.tap.common.extensions.dispatchNavigationAction +import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent +import com.tangem.tap.features.details.ui.securitymode.model.SecurityModeModel +import com.tangem.tap.store +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultSecurityModeComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: SecurityModeComponent.Params, +) : SecurityModeComponent, AppComponentContext by appComponentContext { + + private val model: SecurityModeModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.screenState.collectAsStateWithLifecycle() + + SecurityModeScreen( + modifier = modifier, + state = state, + onBackClick = { store.dispatchNavigationAction(AppRouter::pop) }, + ) + } + + @AssistedFactory + interface Factory : SecurityModeComponent.Factory { + override fun create( + context: AppComponentContext, + params: SecurityModeComponent.Params, + ): DefaultSecurityModeComponent + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt deleted file mode 100644 index 1c5fe7f90b..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.tap.features.details.ui.securitymode - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.fragment.app.viewModels -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.tangem.common.routing.AppRouter -import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.store -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -@AndroidEntryPoint -internal class SecurityModeFragment : ComposeFragment() { - - @Inject - override lateinit var uiDependencies: UiDependencies - - private val viewModel: SecurityModeViewModel by viewModels() - - @Composable - override fun ScreenContent(modifier: Modifier) { - val state by viewModel.screenState.collectAsStateWithLifecycle() - - SecurityModeScreen( - modifier = modifier, - state = state, - onBackClick = { store.dispatchNavigationAction(AppRouter::pop) }, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/api/SecurityModeComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/api/SecurityModeComponent.kt new file mode 100644 index 0000000000..a09d7ba6c9 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/api/SecurityModeComponent.kt @@ -0,0 +1,14 @@ +package com.tangem.tap.features.details.ui.securitymode.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.wallets.models.UserWalletId + +interface SecurityModeComponent : ComposableContentComponent { + + data class Params( + val userWalletId: UserWalletId, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/di/SecurityModeFeatureModule.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/di/SecurityModeFeatureModule.kt new file mode 100644 index 0000000000..3fb8b00635 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/di/SecurityModeFeatureModule.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.features.details.ui.securitymode.di + +import com.tangem.core.decompose.model.Model +import com.tangem.tap.features.details.ui.securitymode.DefaultSecurityModeComponent +import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent +import com.tangem.tap.features.details.ui.securitymode.model.SecurityModeModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface SecurityModeFeatureModule { + + @Binds + fun bindFactory(impl: DefaultSecurityModeComponent.Factory): SecurityModeComponent.Factory + + @Binds + @IntoMap + @ClassKey(SecurityModeModel::class) + fun bindModel(model: SecurityModeModel): Model +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/model/SecurityModeModel.kt similarity index 88% rename from app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt rename to app/src/main/java/com/tangem/tap/features/details/ui/securitymode/model/SecurityModeModel.kt index a7f0b763b3..39ae0e13fb 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/model/SecurityModeModel.kt @@ -1,11 +1,12 @@ -package com.tangem.tap.features.details.ui.securitymode +package com.tangem.tap.features.details.ui.securitymode.model -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.compose.runtime.Stable import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.Analytics +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model import com.tangem.domain.common.util.cardTypesResolver import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -15,18 +16,21 @@ import com.tangem.tap.features.details.redux.SecurityOption import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor import com.tangem.tap.features.details.ui.common.utils.getAllowedSecurityOptions import com.tangem.tap.features.details.ui.common.utils.getCurrentSecurityOption +import com.tangem.tap.features.details.ui.securitymode.SecurityModeScreenState import com.tangem.tap.store -import dagger.hilt.android.lifecycle.HiltViewModel +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject -@HiltViewModel -internal class SecurityModeViewModel @Inject constructor( +@Stable +@ModelScoped +internal class SecurityModeModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, private val tangemSdkManager: TangemSdkManager, private val cardSettingsInteractor: CardSettingsInteractor, -) : ViewModel() { +) : Model() { private val scannedScanResponse = cardSettingsInteractor.scannedScanResponse.value ?: error("Scan response is null") @@ -62,7 +66,7 @@ internal class SecurityModeViewModel @Inject constructor( val cardId = scannedScanResponse.card.cardId val selectedOption = screenState.value.selectedSecurityMode - viewModelScope.launch { + modelScope.launch { val result = when (selectedOption) { SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId) SecurityOption.PassCode -> tangemSdkManager.setPasscode(cardId) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/DefaultWalletConnectComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/DefaultWalletConnectComponent.kt new file mode 100644 index 0000000000..73a0989519 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/DefaultWalletConnectComponent.kt @@ -0,0 +1,70 @@ +package com.tangem.tap.features.details.ui.walletconnect + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.Modifier +import com.arkivanov.essenty.lifecycle.subscribe +import com.tangem.common.routing.AppRouter +import com.tangem.core.analytics.Analytics +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.tap.common.analytics.events.WalletConnect +import com.tangem.tap.common.extensions.dispatchNavigationAction +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState +import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent +import com.tangem.tap.store +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import org.rekotlin.StoreSubscriber + +@Suppress("UnusedPrivateMember") +internal class DefaultWalletConnectComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: Unit, +) : WalletConnectComponent, AppComponentContext by appComponentContext, StoreSubscriber { + + private val model: WalletConnectModel = getOrCreateModel() + + private var screenState: MutableState = + mutableStateOf(model.updateState(store.state.walletConnectState)) + + init { + lifecycle.subscribe( + onCreate = { + Analytics.send(WalletConnect.ScreenOpened()) + }, + onStart = { + store.subscribe(this) { state -> + state.skipRepeats { oldState, newState -> + oldState.walletConnectState == newState.walletConnectState + }.select { it.walletConnectState } + } + }, + onStop = { + store.unsubscribe(this) + }, + ) + } + + override fun newState(state: WalletConnectState) { + screenState.value = model.updateState(state) + } + + @Composable + override fun Content(modifier: Modifier) { + WalletConnectScreen( + modifier = modifier, + state = screenState.value, + onBackClick = { + store.dispatchNavigationAction(AppRouter::pop) + }, + ) + } + + @AssistedFactory + interface Factory : WalletConnectComponent.Factory { + override fun create(context: AppComponentContext, params: Unit): DefaultWalletConnectComponent + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt deleted file mode 100644 index d720c10ec7..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.tangem.tap.features.details.ui.walletconnect - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.Modifier -import androidx.fragment.app.viewModels -import com.tangem.common.routing.AppRouter -import com.tangem.core.analytics.Analytics -import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.tap.common.analytics.events.WalletConnect -import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState -import com.tangem.tap.store -import dagger.hilt.android.AndroidEntryPoint -import org.rekotlin.StoreSubscriber -import javax.inject.Inject - -@AndroidEntryPoint -internal class WalletConnectFragment : ComposeFragment(), StoreSubscriber { - - @Inject - override lateinit var uiDependencies: UiDependencies - - private val viewModel: WalletConnectViewModel by viewModels() - - private var screenState: MutableState? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - Analytics.send(WalletConnect.ScreenOpened()) - lifecycle.addObserver(viewModel) - screenState = mutableStateOf(viewModel.updateState(store.state.walletConnectState)) - } - - @Composable - override fun ScreenContent(modifier: Modifier) { - val state = screenState?.value ?: return - WalletConnectScreen( - modifier = modifier, - state = state, - onBackClick = { - store.dispatchNavigationAction(AppRouter::pop) - }, - ) - } - - override fun onStart() { - super.onStart() - store.subscribe(this) { state -> - state.skipRepeats { oldState, newState -> - oldState.walletConnectState == newState.walletConnectState - }.select { it.walletConnectState } - } - } - - override fun onStop() { - super.onStop() - store.unsubscribe(this) - } - - override fun newState(state: WalletConnectState) { - if (activity == null || view == null) return - screenState?.value = viewModel.updateState(state) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt similarity index 75% rename from app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt rename to app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt index 2ee11076ee..b940ddcc29 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt @@ -1,32 +1,37 @@ package com.tangem.tap.features.details.ui.walletconnect -import androidx.lifecycle.* +import androidx.compose.runtime.Stable import arrow.core.getOrElse +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState import com.tangem.tap.store -import dagger.hilt.android.lifecycle.HiltViewModel +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject -@HiltViewModel -internal class WalletConnectViewModel @Inject constructor( +@Stable +@ModelScoped +internal class WalletConnectModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, private val listenToQrScanningUseCase: ListenToQrScanningUseCase, private val clipboardManager: ClipboardManager, -) : ViewModel(), DefaultLifecycleObserver { +) : Model() { - override fun onCreate(owner: LifecycleOwner) { - viewModelScope.launch { + init { + modelScope.launch { listenToQrScanningUseCase(SourceType.WALLET_CONNECT) .getOrElse { emptyFlow() } - .flowWithLifecycle(owner.lifecycle, minActiveState = Lifecycle.State.CREATED) - .collect { store.dispatch(WalletConnectAction.OpenSession(it)) } + .map { WalletConnectAction.OpenSession(it, WalletConnectAction.OpenSession.SourceType.QR) } + .collect { store.dispatch(it) } } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/api/WalletConnectComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/api/WalletConnectComponent.kt new file mode 100644 index 0000000000..4e1f4ab3fb --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/api/WalletConnectComponent.kt @@ -0,0 +1,8 @@ +package com.tangem.tap.features.details.ui.walletconnect.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent + +interface WalletConnectComponent : ComposableContentComponent { + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/di/WalletConnectFeatureModule.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/di/WalletConnectFeatureModule.kt new file mode 100644 index 0000000000..20a85d18be --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/di/WalletConnectFeatureModule.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.features.details.ui.walletconnect.di + +import com.tangem.core.decompose.model.Model +import com.tangem.tap.features.details.ui.walletconnect.DefaultWalletConnectComponent +import com.tangem.tap.features.details.ui.walletconnect.WalletConnectModel +import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface WalletConnectFeatureModule { + + @Binds + fun bindFactory(impl: DefaultWalletConnectComponent.Factory): WalletConnectComponent.Factory + + @Binds + @IntoMap + @ClassKey(WalletConnectModel::class) + fun bindModel(model: WalletConnectModel): Model +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt index 5411faec75..a6ac03c6c7 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt @@ -17,7 +17,7 @@ object ClipboardOrScanQrDialog { setTitle(context.getString(R.string.common_select_action)) setMessage(context.getText(R.string.wallet_connect_clipboard_alert)) setPositiveButton(context.getText(R.string.wallet_connect_paste_from_clipboard)) { _, _ -> - store.dispatch(WalletConnectAction.OpenSession(wcUri)) + store.dispatch(WalletConnectAction.OpenSession(wcUri, WalletConnectAction.OpenSession.SourceType.ETC)) } setNegativeButton(context.getText(R.string.wallet_connect_scan_new_code)) { _, _ -> store.dispatchNavigationAction { diff --git a/app/src/main/java/com/tangem/tap/features/home/DefaultHomeComponent.kt b/app/src/main/java/com/tangem/tap/features/home/DefaultHomeComponent.kt new file mode 100644 index 0000000000..a7a853a86a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/DefaultHomeComponent.kt @@ -0,0 +1,74 @@ +package com.tangem.tap.features.home + +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import com.arkivanov.essenty.lifecycle.subscribe +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.components.SystemBarsIconsDisposable +import com.tangem.core.ui.utils.findActivity +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.features.home.api.HomeComponent +import com.tangem.tap.features.home.compose.StoriesScreen +import com.tangem.tap.features.home.redux.HomeAction +import com.tangem.tap.features.home.redux.HomeState +import com.tangem.tap.store +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import org.rekotlin.StoreSubscriber + +@Suppress("UnusedPrivateMember") +internal class DefaultHomeComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: Unit, +) : HomeComponent, AppComponentContext by appComponentContext, StoreSubscriber { + + private val model: HomeModel = getOrCreateModel() + + private var homeState: MutableState = mutableStateOf(store.state.homeState) + + init { + lifecycle.subscribe( + onCreate = { + store.dispatch(HomeAction.OnCreate) + }, + onStart = { + store.subscribe(subscriber = this) { state -> + state + .skipRepeats { oldState, newState -> oldState.homeState == newState.homeState } + .select(AppState::homeState) + } + }, + onStop = { + store.unsubscribe(this) + }, + ) + } + + @Composable + override fun Content(modifier: Modifier) { + val activity = LocalContext.current.findActivity() + BackHandler(onBack = activity::finish) + SystemBarsIconsDisposable(darkIcons = false) + StoriesScreen( + homeState = homeState, + onScanButtonClick = model::onScanClick, + onShopButtonClick = model::onShopClick, + onSearchTokensClick = model::onSearchClick, + ) + } + + override fun newState(state: HomeState) { + homeState.value = state + } + + @AssistedFactory + interface Factory : HomeComponent.Factory { + override fun create(context: AppComponentContext, params: Unit): DefaultHomeComponent + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt deleted file mode 100644 index 2379b399a9..0000000000 --- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt +++ /dev/null @@ -1,75 +0,0 @@ -package com.tangem.tap.features.home - -import android.os.Bundle -import androidx.activity.compose.BackHandler -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.Modifier -import androidx.fragment.app.viewModels -import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.components.SystemBarsIconsDisposable -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.tap.common.redux.AppState -import com.tangem.tap.features.home.compose.StoriesScreen -import com.tangem.tap.features.home.redux.HomeAction -import com.tangem.tap.features.home.redux.HomeState -import com.tangem.tap.store -import dagger.hilt.android.AndroidEntryPoint -import org.rekotlin.StoreSubscriber -import javax.inject.Inject - -@AndroidEntryPoint -internal class HomeFragment : ComposeFragment(), StoreSubscriber { - - @Inject - override lateinit var uiDependencies: UiDependencies - - private var homeState: MutableState = mutableStateOf(store.state.homeState) - - private val viewModel by viewModels() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - store.dispatch(HomeAction.OnCreate) - } - - @Composable - override fun ScreenContent(modifier: Modifier) { - BackHandler(onBack = requireActivity()::finish) - SystemBarsIconsDisposable(darkIcons = false) - ScreenContent() - } - - override fun onStart() { - super.onStart() - - store.subscribe(subscriber = this) { state -> - state - .skipRepeats { oldState, newState -> oldState.homeState == newState.homeState } - .select(AppState::homeState) - } - } - - override fun onStop() { - super.onStop() - store.unsubscribe(this) - } - - override fun newState(state: HomeState) { - if (activity == null || view == null) return - - homeState.value = state - } - - @Suppress("TopLevelComposableFunctions") - @Composable - private fun ScreenContent() { - StoriesScreen( - homeState = homeState, - onScanButtonClick = viewModel::onScanClick, - onShopButtonClick = viewModel::onShopClick, - onSearchTokensClick = viewModel::onSearchClick, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeViewModel.kt b/app/src/main/java/com/tangem/tap/features/home/HomeModel.kt similarity index 93% rename from app/src/main/java/com/tangem/tap/features/home/HomeViewModel.kt rename to app/src/main/java/com/tangem/tap/features/home/HomeModel.kt index f0b0d760e6..8511f53ab2 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeModel.kt @@ -1,7 +1,6 @@ package com.tangem.tap.features.home -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.compose.runtime.Stable import com.google.firebase.analytics.ktx.analytics import com.google.firebase.ktx.Firebase import com.tangem.common.routing.AppRoute @@ -10,6 +9,8 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model import com.tangem.core.navigation.url.UrlOpener import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.repository.CardSdkConfigRepository @@ -30,7 +31,7 @@ import com.tangem.tap.features.home.redux.HIDE_PROGRESS_DELAY import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL import com.tangem.tap.store -import dagger.hilt.android.lifecycle.HiltViewModel +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -38,8 +39,10 @@ import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList") -@HiltViewModel -internal class HomeViewModel @Inject constructor( +@Stable +@ModelScoped +internal class HomeModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, private val scanCardProcessor: ScanCardProcessor, private val generateWalletNameUseCase: GenerateWalletNameUseCase, private val saveWalletUseCase: SaveWalletUseCase, @@ -47,7 +50,7 @@ internal class HomeViewModel @Inject constructor( private val settingsRepository: SettingsRepository, private val urlOpener: UrlOpener, private val analyticsEventHandler: AnalyticsEventHandler, -) : ViewModel() { +) : Model() { private val tangemErrorHandler = TangemTangemErrorsHandler(store) @@ -73,7 +76,7 @@ internal class HomeViewModel @Inject constructor( } private fun scanCard() { - viewModelScope.launch { + modelScope.launch { cardSdkConfigRepository.isBiometricsRequestPolicy = settingsRepository.shouldSaveAccessCodes() scanCardProcessor.scan( diff --git a/app/src/main/java/com/tangem/tap/features/home/api/HomeComponent.kt b/app/src/main/java/com/tangem/tap/features/home/api/HomeComponent.kt new file mode 100644 index 0000000000..0c63e10a1c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/api/HomeComponent.kt @@ -0,0 +1,9 @@ +package com.tangem.tap.features.home.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent + +interface HomeComponent : ComposableContentComponent { + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt index 0a1f0fdaed..1635f234ad 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt @@ -6,7 +6,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.material.Text +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt index 184bf8d37d..42fdcaf8fa 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt @@ -7,7 +7,7 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.Text +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember diff --git a/app/src/main/java/com/tangem/tap/features/home/di/HomeFeatureModule.kt b/app/src/main/java/com/tangem/tap/features/home/di/HomeFeatureModule.kt new file mode 100644 index 0000000000..9f92cc5583 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/di/HomeFeatureModule.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.features.home.di + +import com.tangem.core.decompose.model.Model +import com.tangem.tap.features.home.DefaultHomeComponent +import com.tangem.tap.features.home.HomeModel +import com.tangem.tap.features.home.api.HomeComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface HomeFeatureModule { + + @Binds + fun bindFactory(impl: DefaultHomeComponent.Factory): HomeComponent.Factory + + @Binds + @IntoMap + @ClassKey(HomeModel::class) + fun bindModel(model: HomeModel): Model +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 502e216db1..2d53df4f9b 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -75,7 +75,8 @@ object OnboardingHelper { scanResponse.productType == ProductType.Ring || scanResponse.productType == ProductType.Wallet || scanResponse.productType == ProductType.Visa // AppRoute.OnboardingOther is also supported - if (store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled && + val featureToggles = store.inject(DaggerGraphState::onboardingV2FeatureToggles) + if (featureToggles.isOnboardingV2Enabled && newOnboardingSupportTypes ) { val backupState = store.state.onboardingWalletState.backupState @@ -91,7 +92,15 @@ object OnboardingHelper { } return when (val type = scanResponse.productType) { - ProductType.Note -> AppRoute.OnboardingNote + ProductType.Note -> if (featureToggles.isNoteRefactoringEnabled) { + AppRoute.Onboarding( + scanResponse = scanResponse, + startFromBackup = false, + mode = AppRoute.Onboarding.Mode.Onboarding, + ) + } else { + AppRoute.OnboardingNote + } ProductType.Wallet, ProductType.Wallet2, ProductType.Ring, diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt index c23f77abe1..403c914016 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt @@ -3,9 +3,9 @@ package com.tangem.tap.features.saveWallet.ui.components import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/app/src/main/java/com/tangem/tap/features/welcome/di/ModelModule.kt b/app/src/main/java/com/tangem/tap/features/welcome/di/ModelModule.kt index 1a90bd0bc5..92e347fe26 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/di/ModelModule.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/di/ModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.tap.features.welcome.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.tap.features.welcome.model.WelcomeModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface ModelModule { @Binds diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt index c9bbbdf31c..433f9521fa 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt @@ -3,8 +3,8 @@ package com.tangem.tap.features.welcome.ui.components import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.material.Icon -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/app/src/main/java/com/tangem/tap/network/auth/DefaultVisaAuthProvider.kt b/app/src/main/java/com/tangem/tap/network/auth/DefaultVisaAuthProvider.kt deleted file mode 100644 index 0a793b0222..0000000000 --- a/app/src/main/java/com/tangem/tap/network/auth/DefaultVisaAuthProvider.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.tangem.tap.network.auth - -import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider -import com.tangem.domain.visa.model.VisaCardActivationStatus -import com.tangem.domain.visa.model.getAuthHeader -import com.tangem.domain.wallets.legacy.UserWalletsListManager -import javax.inject.Inject - -internal class DefaultVisaAuthProvider @Inject constructor( - private val userWalletsListManager: UserWalletsListManager, -) : TangemVisaAuthProvider { - - override suspend fun getAuthHeader(cardId: String): String { - val card = userWalletsListManager.userWalletsSync.firstOrNull { it.cardId == cardId } - val status = card?.scanResponse?.visaCardActivationStatus as? VisaCardActivationStatus.Activated - ?: return "Error in the app!" - return status.visaAuthTokens.getAuthHeader() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt b/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt index 829f08d9cc..c72a9c145c 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt @@ -1,21 +1,17 @@ package com.tangem.tap.network.auth.di import com.tangem.datasource.api.common.AuthProvider -import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.lib.auth.ExpressAuthProvider import com.tangem.lib.auth.StakeKitAuthProvider -import com.tangem.tap.network.auth.* import com.tangem.tap.network.auth.DefaultAppVersionProvider import com.tangem.tap.network.auth.DefaultAuthProvider import com.tangem.tap.network.auth.DefaultExpressAuthProvider import com.tangem.tap.network.auth.DefaultStakeKitAuthProvider -import com.tangem.tap.network.auth.DefaultVisaAuthProvider import com.tangem.utils.version.AppVersionProvider -import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -55,13 +51,4 @@ internal class AuthModule { fun provideAppVersionProvider(): AppVersionProvider { return DefaultAppVersionProvider() } -} - -@Module -@InstallIn(SingletonComponent::class) -internal interface AuthBindModule { - - @Binds - @Singleton - fun bindVisaAuthProvider(authStorage: DefaultVisaAuthProvider): TangemVisaAuthProvider } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index ca9bb24e80..2e0518e079 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -26,6 +26,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.runCatching import com.tangem.utils.isNullOrZero import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.withContext @Suppress("LongParameterList") @@ -135,11 +136,13 @@ internal class DefaultRampManager( cryptoCurrency: CryptoCurrency, ): ExchangeableState { return withContext(dispatchers.io) { - when (val asset = expressServiceLoader.getInitializationStatus(userWalletId).value) { + val initializationStatus = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull() + ?: return@withContext ExchangeableState.Loading + when (initializationStatus) { is Lce.Error -> ExchangeableState.Error is Lce.Loading -> ExchangeableState.Loading is Lce.Content -> { - val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) } + val foundAsset = initializationStatus.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) } foundAsset?.exchangeAvailable?.toExchangeableState() ?: ExchangeableState.AssetNotFound } } diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoBlockchainMapping.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoBlockchainMapping.kt index 8a647aeee9..3c20cb9f8e 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoBlockchainMapping.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoBlockchainMapping.kt @@ -153,6 +153,8 @@ internal val Blockchain.mercuryoNetwork: String? Blockchain.Bitrock, Blockchain.BitrockTestnet -> null Blockchain.Sonic, Blockchain.SonicTestnet -> null Blockchain.ApeChain, Blockchain.ApeChainTestnet -> null + Blockchain.Scroll, Blockchain.ScrollTestnet -> null + Blockchain.ZkLinkNova, Blockchain.ZkLinkNovaTestnet -> null Blockchain.KaspaTestnet -> null } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonpayBlockchainMapping.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonpayBlockchainMapping.kt index aa3442e13d..a0f0c7f157 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonpayBlockchainMapping.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonpayBlockchainMapping.kt @@ -154,5 +154,7 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency? Bitrock, BitrockTestnet -> null Sonic, SonicTestnet -> null ApeChain, ApeChainTestnet -> null + Scroll, ScrollTestnet -> null + ZkLinkNova, ZkLinkNovaTestnet -> null KaspaTestnet -> null } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 3c36d9833b..08b6c9ba8a 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -33,7 +33,6 @@ import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles import com.tangem.features.onramp.OnrampFeatureToggles -import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor @@ -67,7 +66,6 @@ data class DaggerGraphState( val urlOpener: UrlOpener? = null, val shareManager: ShareManager? = null, val appRouter: AppRouter? = null, - val pushNotificationsRouter: PushNotificationsRouter? = null, val transactionSignerFactory: TransactionSignerFactory? = null, val getUserCountryUseCase: GetUserCountryUseCase? = null, val onrampFeatureToggles: OnrampFeatureToggles? = null, diff --git a/app/src/main/java/com/tangem/tap/routing/RootContent.kt b/app/src/main/java/com/tangem/tap/routing/RootContent.kt index 077688613f..669e7f44a3 100644 --- a/app/src/main/java/com/tangem/tap/routing/RootContent.kt +++ b/app/src/main/java/com/tangem/tap/routing/RootContent.kt @@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat.startActivity -import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children +import com.arkivanov.decompose.extensions.compose.stack.Children import com.arkivanov.decompose.router.stack.ChildStack import com.tangem.common.routing.AppRoute import com.tangem.core.ui.UiDependencies diff --git a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt index adde5d9894..cafffe14d3 100644 --- a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt +++ b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt @@ -4,11 +4,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.stack.ChildStack import com.arkivanov.decompose.router.stack.childStack import com.arkivanov.decompose.value.Value -import com.arkivanov.decompose.value.observe +import com.arkivanov.decompose.value.subscribe import com.arkivanov.essenty.backhandler.BackCallback import com.arkivanov.essenty.lifecycle.doOnDestroy import com.google.android.material.snackbar.Snackbar @@ -65,7 +65,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor( appRouterConfig.componentRouter = router appRouterConfig.snackbarHandler = this - stack.observe(lifecycle) { stack -> + stack.subscribe(lifecycle) { stack -> val stackItems = stack.items.map { it.configuration } if (appRouterConfig.stack != stackItems) { diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index f1217ef2d0..a852857c6d 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -2,8 +2,8 @@ package com.tangem.tap.routing.utils import com.tangem.common.routing.AppRoute import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.feature.qrscanning.QrScanningRouter -import com.tangem.feature.referral.ReferralFragment +import com.tangem.feature.qrscanning.QrScanningComponent +import com.tangem.feature.referral.api.ReferralComponent import com.tangem.feature.stories.api.StoriesComponent import com.tangem.feature.walletsettings.component.WalletSettingsComponent import com.tangem.features.details.component.DetailsComponent @@ -13,21 +13,21 @@ import com.tangem.features.managetokens.component.ManageTokensSource import com.tangem.features.markets.details.MarketsTokenDetailsComponent import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent import com.tangem.features.onramp.component.* -import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter -import com.tangem.features.send.api.navigation.SendRouter -import com.tangem.features.staking.api.StakingComponent +import com.tangem.features.pushnotifications.api.PushNotificationsComponent +import com.tangem.features.send.api.SendComponent import com.tangem.features.swap.SwapComponent +import com.tangem.features.staking.api.StakingComponent import com.tangem.features.tester.api.TesterRouter -import com.tangem.features.tokendetails.navigation.TokenDetailsRouter -import com.tangem.features.wallet.navigation.WalletRouter -import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorFragment -import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment -import com.tangem.tap.features.details.ui.cardsettings.CardSettingsFragment -import com.tangem.tap.features.details.ui.cardsettings.coderecovery.AccessCodeRecoveryFragment -import com.tangem.tap.features.details.ui.resetcard.ResetCardFragment -import com.tangem.tap.features.details.ui.securitymode.SecurityModeFragment -import com.tangem.tap.features.details.ui.walletconnect.WalletConnectFragment -import com.tangem.tap.features.home.HomeFragment +import com.tangem.features.tokendetails.TokenDetailsComponent +import com.tangem.features.wallet.WalletEntryComponent +import com.tangem.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent +import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent +import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent +import com.tangem.tap.features.details.ui.cardsettings.coderecovery.api.AccessCodeRecoveryComponent +import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent +import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent +import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent +import com.tangem.tap.features.home.api.HomeComponent import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCardsFragment import com.tangem.tap.features.onboarding.products.twins.ui.OnboardingTwinsFragment @@ -58,14 +58,23 @@ internal class ChildFactory @Inject constructor( private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory, private val welcomeComponentFactory: WelcomeComponent.Factory, private val storiesComponentFactory: StoriesComponent.Factory, + private val sendComponentFactory: SendComponent.Factory, private val stakingComponentFactory: StakingComponent.Factory, private val swapComponentFactory: SwapComponent.Factory, - private val sendRouter: SendRouter, - private val tokenDetailsRouter: TokenDetailsRouter, - private val walletRouter: WalletRouter, - private val qrScanningRouter: QrScanningRouter, + private val homeComponentFactory: HomeComponent.Factory, + private val tokenDetailsComponentFactory: TokenDetailsComponent.Factory, + private val walletConnectComponentFactory: WalletConnectComponent.Factory, + private val qrScanningComponentFactory: QrScanningComponent.Factory, + private val accessCodeRecoveryComponentFactory: AccessCodeRecoveryComponent.Factory, + private val cardSettingsComponentFactory: CardSettingsComponent.Factory, + private val appCurrencySelectorComponentFactory: AppCurrencySelectorComponent.Factory, + private val appSettingsComponentFactory: AppSettingsComponent.Factory, + private val securityModeComponentFactory: SecurityModeComponent.Factory, + private val resetCardComponentFactory: ResetCardComponent.Factory, + private val referralComponentFactory: ReferralComponent.Factory, + private val pushNotificationsComponentFactory: PushNotificationsComponent.Factory, + private val walletComponentFactory: WalletEntryComponent.Factory, private val testerRouter: TesterRouter, - private val pushNotificationRouter: PushNotificationsRouter, private val routingFeatureToggles: RoutingFeatureToggles, ) { @@ -213,6 +222,16 @@ internal class ChildFactory @Inject constructor( componentFactory = storiesComponentFactory, ) } + is AppRoute.CurrencyDetails -> { + createComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = TokenDetailsComponent.Params( + userWalletId = route.userWalletId, + currency = route.currency, + ), + componentFactory = tokenDetailsComponentFactory, + ) + } is AppRoute.Staking -> { createComponentChild( contextProvider = contextProvider(route, contextFactory), @@ -237,25 +256,119 @@ internal class ChildFactory @Inject constructor( componentFactory = swapComponentFactory, ) } - is AppRoute.AccessCodeRecovery, - is AppRoute.AppCurrencySelector, - is AppRoute.SaveWallet, - is AppRoute.Send, - is AppRoute.AppSettings, - is AppRoute.CardSettings, - is AppRoute.DetailsSecurity, - is AppRoute.Home, + is AppRoute.Send -> { + createComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = SendComponent.Params( + userWalletId = route.userWalletId, + currency = route.currency, + transactionId = route.transactionId, + amount = route.amount, + tag = route.tag, + destinationAddress = route.destinationAddress, + ), + componentFactory = sendComponentFactory, + ) + } + is AppRoute.Home -> { + createComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = Unit, + componentFactory = homeComponentFactory, + ) + } + is AppRoute.WalletConnectSessions -> { + createComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = Unit, + componentFactory = walletConnectComponentFactory, + ) + } + is AppRoute.QrScanning -> { + createComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = QrScanningComponent.Params( + source = route.source, + networkName = route.networkName, + ), + componentFactory = qrScanningComponentFactory, + ) + } + is AppRoute.AccessCodeRecovery -> { + createComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = Unit, + componentFactory = accessCodeRecoveryComponentFactory, + ) + } + is AppRoute.CardSettings -> { + createComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = CardSettingsComponent.Params(userWalletId = route.userWalletId), + componentFactory = cardSettingsComponentFactory, + ) + } + is AppRoute.AppCurrencySelector -> { + createComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = Unit, + componentFactory = appCurrencySelectorComponentFactory, + ) + } + is AppRoute.AppSettings -> { + createComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = Unit, + componentFactory = appSettingsComponentFactory, + ) + } + is AppRoute.DetailsSecurity -> { + createComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = SecurityModeComponent.Params( + userWalletId = route.userWalletId, + ), + componentFactory = securityModeComponentFactory, + ) + } + is AppRoute.ResetToFactory -> { + createComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = ResetCardComponent.Params( + userWalletId = route.userWalletId, + cardId = route.cardId, + isActiveBackupStatus = route.isActiveBackupStatus, + backupCardsCount = route.backupCardsCount, + ), + componentFactory = resetCardComponentFactory, + ) + } + is AppRoute.ReferralProgram -> { + createComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = ReferralComponent.Params(route.userWalletId), + componentFactory = referralComponentFactory, + ) + } + is AppRoute.PushNotification -> { + createComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = Unit, + componentFactory = pushNotificationsComponentFactory, + ) + } + is AppRoute.Wallet -> { + createComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = Unit, + componentFactory = walletComponentFactory, + ) + } is AppRoute.OnboardingNote, + is AppRoute.SaveWallet, is AppRoute.OnboardingOther, is AppRoute.OnboardingTwins, is AppRoute.OnboardingWallet, - is AppRoute.QrScanning, - is AppRoute.ReferralProgram, - is AppRoute.ResetToFactory, - is AppRoute.Wallet, - is AppRoute.WalletConnectSessions, - is AppRoute.CurrencyDetails, - is AppRoute.PushNotification, -> error("Unsupported route: $route") } // endregion @@ -271,22 +384,49 @@ internal class ChildFactory @Inject constructor( Child.Initial } is AppRoute.AccessCodeRecovery -> { - route.asFragmentChild(Provider { AccessCodeRecoveryFragment() }) + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = Unit, + componentFactory = accessCodeRecoveryComponentFactory, + ) } is AppRoute.AppCurrencySelector -> { - route.asFragmentChild(Provider { AppCurrencySelectorFragment() }) + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = Unit, + componentFactory = appCurrencySelectorComponentFactory, + ) } is AppRoute.SaveWallet -> { route.asFragmentChild(Provider { SaveWalletBottomSheetFragment() }) } is AppRoute.Send -> { - route.asFragmentChild(Provider { sendRouter.getEntryFragment() }) + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = SendComponent.Params( + userWalletId = route.userWalletId, + currency = route.currency, + transactionId = route.transactionId, + amount = route.amount, + tag = route.tag, + destinationAddress = route.destinationAddress, + ), + componentFactory = sendComponentFactory, + ) } is AppRoute.AppSettings -> { - route.asFragmentChild(Provider { AppSettingsFragment() }) + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = Unit, + componentFactory = appSettingsComponentFactory, + ) } is AppRoute.CardSettings -> { - route.asFragmentChild(Provider { CardSettingsFragment() }) + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = CardSettingsComponent.Params(userWalletId = route.userWalletId), + componentFactory = cardSettingsComponentFactory, + ) } is AppRoute.Details -> { route.asComponentChild( @@ -296,7 +436,13 @@ internal class ChildFactory @Inject constructor( ) } is AppRoute.DetailsSecurity -> { - route.asFragmentChild(Provider { SecurityModeFragment() }) + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = SecurityModeComponent.Params( + userWalletId = route.userWalletId, + ), + componentFactory = securityModeComponentFactory, + ) } is AppRoute.Disclaimer -> { route.asComponentChild( @@ -306,7 +452,11 @@ internal class ChildFactory @Inject constructor( ) } is AppRoute.Home -> { - route.asFragmentChild(Provider { HomeFragment() }) + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = Unit, + componentFactory = homeComponentFactory, + ) } is AppRoute.ManageTokens -> { val source = when (route.source) { @@ -334,13 +484,33 @@ internal class ChildFactory @Inject constructor( route.asFragmentChild(Provider { OnboardingWalletFragment() }) } is AppRoute.QrScanning -> { - route.asFragmentChild(Provider { qrScanningRouter.getEntryFragment() }) + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = QrScanningComponent.Params( + source = route.source, + networkName = route.networkName, + ), + componentFactory = qrScanningComponentFactory, + ) } is AppRoute.ReferralProgram -> { - route.asFragmentChild(Provider { ReferralFragment() }) + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = ReferralComponent.Params(route.userWalletId), + componentFactory = referralComponentFactory, + ) } is AppRoute.ResetToFactory -> { - route.asFragmentChild(Provider { ResetCardFragment() }) + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = ResetCardComponent.Params( + userWalletId = route.userWalletId, + cardId = route.cardId, + isActiveBackupStatus = route.isActiveBackupStatus, + backupCardsCount = route.backupCardsCount, + ), + componentFactory = resetCardComponentFactory, + ) } is AppRoute.Swap -> { route.asComponentChild( @@ -356,13 +526,28 @@ internal class ChildFactory @Inject constructor( ) } is AppRoute.Wallet -> { - route.asFragmentChild(Provider { walletRouter.getEntryFragment() }) + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = Unit, + componentFactory = walletComponentFactory, + ) } is AppRoute.WalletConnectSessions -> { - route.asFragmentChild(Provider { WalletConnectFragment() }) + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = Unit, + componentFactory = walletConnectComponentFactory, + ) } is AppRoute.CurrencyDetails -> { - route.asFragmentChild(Provider { tokenDetailsRouter.getEntryFragment() }) + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = TokenDetailsComponent.Params( + userWalletId = route.userWalletId, + currency = route.currency, + ), + componentFactory = tokenDetailsComponentFactory, + ) } is AppRoute.Welcome -> { route.asFragmentChild(Provider { WelcomeFragment() }) @@ -382,7 +567,11 @@ internal class ChildFactory @Inject constructor( ) } is AppRoute.PushNotification -> { - route.asFragmentChild(Provider { pushNotificationRouter.entryFragment() }) + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = Unit, + componentFactory = pushNotificationsComponentFactory, + ) } is AppRoute.WalletSettings -> { route.asComponentChild( diff --git a/build.gradle.kts b/build.gradle.kts index 0ace95bd3b..4cbeef6686 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,6 +12,8 @@ plugins { alias(deps.plugins.firebase.crashlytics) apply false alias(deps.plugins.firebase.perf) apply false alias(deps.plugins.room) apply false + alias(deps.plugins.kotlin.compose.compiler) apply false + alias(deps.plugins.ksp) apply false } val clean by tasks.registering { diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index d7a1d74eeb..914a9ad067 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -40,6 +40,7 @@ sealed class AppRoute(val path: String) : Route { val isTosAccepted: Boolean, ) : AppRoute(path = "/disclaimer${if (isTosAccepted) "/tos_accepted" else ""}") + @Deprecated("Do not use! Should be replaced by an implementation in onboarding component") @Serializable data object OnboardingNote : AppRoute(path = "/onboarding/note") @@ -68,15 +69,7 @@ sealed class AppRoute(val path: String) : Route { data class CurrencyDetails( val userWalletId: UserWalletId, val currency: CryptoCurrency, - ) : AppRoute(path = "/currency_details/${userWalletId.stringValue}/${currency.id.value}"), RouteBundleParams { - - override fun getBundle(): Bundle = bundle(serializer()) - - companion object { - const val USER_WALLET_ID_KEY = "userWalletId" - const val CRYPTO_CURRENCY_KEY = "currency" - } - } + ) : AppRoute(path = "/currency_details/${userWalletId.stringValue}/${currency.id.value}") @Serializable data class Send( @@ -92,20 +85,7 @@ sealed class AppRoute(val path: String) : Route { "&$amount" + "&$tag" + "&$destinationAddress", - ), - RouteBundleParams { - - override fun getBundle(): Bundle = bundle(serializer()) - - companion object { - const val USER_WALLET_ID_KEY = "userWalletId" - const val CRYPTO_CURRENCY_KEY = "currency" - const val TRANSACTION_ID_KEY = "transactionId" - const val AMOUNT_KEY = "amount" - const val TAG_KEY = "tag" - const val DESTINATION_ADDRESS_KEY = "destinationAddress" - } - } + ) @Serializable data class Details( @@ -115,22 +95,12 @@ sealed class AppRoute(val path: String) : Route { @Serializable data class DetailsSecurity( val userWalletId: UserWalletId, - ) : AppRoute(path = "/details/security"), RouteBundleParams { - - override fun getBundle(): Bundle = bundle(serializer()) - } + ) : AppRoute(path = "/details/security") @Serializable data class CardSettings( val userWalletId: UserWalletId, - ) : AppRoute(path = "/card_settings/${userWalletId.stringValue}"), RouteBundleParams { - - override fun getBundle(): Bundle = bundle(serializer()) - - companion object { - const val USER_WALLET_ID_KEY = "userWalletId" - } - } + ) : AppRoute(path = "/card_settings/${userWalletId.stringValue}") @Serializable data object AppSettings : AppRoute(path = "/app_settings") @@ -155,31 +125,16 @@ sealed class AppRoute(val path: String) : Route { "/$cardId" + "/$isActiveBackupStatus" + "/$backupCardsCount", - ), - RouteBundleParams { - - override fun getBundle(): Bundle = bundle(serializer()) - - companion object { - const val USER_WALLET_ID = "userWalletId" - const val CARD_ID = "cardId" - const val IS_ACTIVE_BACKUP_STATUS = "isActiveBackupStatus" - const val BACKUP_CARDS_COUNT = "backupCardsCount" - } - } + ) @Serializable - data object AccessCodeRecovery : AppRoute(path = "/access_code_recovery"), RouteBundleParams { - - override fun getBundle(): Bundle = bundle(serializer()) - } + data object AccessCodeRecovery : AppRoute(path = "/access_code_recovery") @Serializable data class ManageTokens( val source: Source, val userWalletId: UserWalletId? = null, - ) : AppRoute(path = "${source.name.lowercase()}/manage_tokens/$userWalletId"), RouteBundleParams { - override fun getBundle(): Bundle = bundle(serializer()) + ) : AppRoute(path = "${source.name.lowercase()}/manage_tokens/$userWalletId") { enum class Source { STORIES, @@ -195,27 +150,12 @@ sealed class AppRoute(val path: String) : Route { data class QrScanning( val source: SourceType, val networkName: String? = null, - ) : AppRoute(path = "/$source/qr_scanning${if (networkName != null) "/$networkName" else ""}"), RouteBundleParams { - - override fun getBundle(): Bundle = bundle(serializer()) - - companion object { - const val SOURCE_KEY = "source" - const val NETWORK_KEY = "networkName" - } - } + ) : AppRoute(path = "/$source/qr_scanning${if (networkName != null) "/$networkName" else ""}") @Serializable data class ReferralProgram( val userWalletId: UserWalletId, - ) : AppRoute(path = "/referral_program"), RouteBundleParams { - - override fun getBundle(): Bundle = bundle(serializer()) - - companion object { - const val USER_WALLET_ID_KEY = "userWalletId" - } - } + ) : AppRoute(path = "/referral_program") @Serializable data class Swap( @@ -230,22 +170,12 @@ sealed class AppRoute(val path: String) : Route { "/${currencyTo?.id?.value}" + "/${userWalletId.stringValue}" + "/$isInitialReverseOrder", - ), - RouteBundleParams { - - override fun getBundle(): Bundle = bundle(serializer()) - - companion object { - const val CURRENCY_FROM_KEY = "currencyFrom" - const val CURRENCY_TO_KEY = "currencyTo" - const val USER_WALLET_ID_KEY = "userWalletId" - const val IS_INITIAL_REVERSE_ORDER = "isInitialReverseOrder" - } - } + ) @Serializable data object TesterMenu : AppRoute(path = "/tester_menu") + @Deprecated("Do not use! Should be replaced by an implementation in wallet route component") @Serializable data object SaveWallet : AppRoute(path = "/save_wallet") @@ -337,7 +267,5 @@ sealed class AppRoute(val path: String) : Route { val storyId: String, val nextScreen: AppRoute, val screenSource: String, - ) : AppRoute(path = "/stories$storyId"), RouteBundleParams { - override fun getBundle(): Bundle = bundle(serializer()) - } + ) : AppRoute(path = "/stories$storyId") } \ No newline at end of file diff --git a/common/ui/build.gradle.kts b/common/ui/build.gradle.kts index a59d399816..42a053ad9b 100644 --- a/common/ui/build.gradle.kts +++ b/common/ui/build.gradle.kts @@ -27,6 +27,7 @@ dependencies { /** Project - Common */ implementation(projects.core.ui) implementation(projects.core.utils) + implementation(projects.libs.crypto) /** Project - Domain */ implementation(projects.domain.appCurrency.models) diff --git a/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsState.kt b/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsState.kt index c534f12308..e7788a87df 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsState.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsState.kt @@ -8,7 +8,7 @@ sealed class NavigationButtonsState { data object Empty : NavigationButtonsState() data class Data( - val primaryButton: NavigationButton, + val primaryButton: NavigationButton?, val prevButton: NavigationButton?, val extraButtons: ImmutableList, val txUrl: String? = null, diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt index cb008ba04a..e37d15ae31 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt @@ -297,6 +297,21 @@ sealed class NotificationUM(val config: NotificationConfig) { ) } + sealed interface Sui { + + data object NotEnoughCoinForTokenTransaction : Error( + title = resourceReference(id = R.string.sui_not_enough_coin_for_fee_title), + subtitle = resourceReference( + id = R.string.sui_not_enough_coin_for_fee_description, + formatArgs = wrappedList( + BigDecimal.ONE.format { + crypto(Blockchain.Sui.currency, Blockchain.Sui.decimals()) + }, + ), + ), + ) + } + sealed interface Koinos { data class InsufficientRecoverableMana( val mana: BigDecimal, diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt index 78d11c9b2f..347ac937ec 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt @@ -66,6 +66,8 @@ object NotificationsFactory { }, ), ) + is GetFeeError.BlockchainErrors.SuiOneCoinRequired -> + add(NotificationUM.Sui.NotEnoughCoinForTokenTransaction) is GetFeeError.DataError, is GetFeeError.UnknownError, -> add( @@ -364,6 +366,7 @@ object NotificationsFactory { ) } } + else -> return } } diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt index c08a010371..c33c364110 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt @@ -15,6 +15,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.StatusSource import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.StringsSigns.DASH_SIGN import com.tangem.utils.converter.Converter import com.tangem.utils.extensions.isZero @@ -199,7 +200,11 @@ class TokenItemStateConverter( is CryptoCurrencyStatus.NoAccount, -> { TokenItemState.Subtitle2State.TextContent( - text = status.getFormattedCryptoAmount(includeStaking = true), + text = status.getFormattedCryptoAmount( + includeStaking = BlockchainUtils.isIncludeStakingTotalBalance( + status.currency.network.id.value, + ), + ), isFlickering = status.value.isFlickering(), ) } @@ -222,7 +227,12 @@ class TokenItemStateConverter( is CryptoCurrencyStatus.NoAccount, -> { TokenItemState.FiatAmountState.Content( - text = status.getFormattedFiatAmount(appCurrency = appCurrency, includeStaking = true), + text = status.getFormattedFiatAmount( + appCurrency = appCurrency, + includeStaking = BlockchainUtils.isIncludeStakingTotalBalance( + status.currency.network.id.value, + ), + ), isFlickering = status.value.isFlickering(), icons = buildList { if (!status.getStakedBalance().isZero()) { diff --git a/core/config-toggles/build.gradle.kts b/core/config-toggles/build.gradle.kts index 97534f26fa..96fb87b639 100644 --- a/core/config-toggles/build.gradle.kts +++ b/core/config-toggles/build.gradle.kts @@ -5,6 +5,7 @@ plugins { alias(deps.plugins.kotlin.android) alias(deps.plugins.kotlin.kapt) alias(deps.plugins.hilt.android) + alias(deps.plugins.ksp) id("configuration") } @@ -24,7 +25,7 @@ dependencies { implementation(deps.moshi) implementation(deps.moshi.kotlin) implementation(deps.timber) - kaptForObfuscatingVariants(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) /** Core modules */ implementation(projects.core.datasource) diff --git a/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json b/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json index f3fc207299..11acf26497 100644 --- a/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json +++ b/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json @@ -26,5 +26,13 @@ { "name": "alephium", "version": "5.21.0" + }, + { + "name": "scroll", + "version": "undefined" + }, + { + "name": "zklink", + "version": "undefined" } ] \ No newline at end of file diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index cd7443d27d..20a7e31c0b 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -46,5 +46,25 @@ { "name": "BALANCES_CACHING_ENABLED", "version": "5.21.0" + }, + { + "name": "NFT_ENABLED", + "version": "undefined" + }, + { + "name": "STAKING_CARDANO_ENABLED", + "version": "undefined" + }, + { + "name": "TX_HISTORY_REFACTORING_ENABLED", + "version": "undefined" + }, + { + "name": "ASK_BIOMETRY_REFACTORING_ENABLED", + "version": "undefined" + }, + { + "name": "NOTE_REFACTORING_ENABLED", + "version": "undefined" } ] diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 696989eaf7..97550bec4e 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -6,6 +6,7 @@ plugins { alias(deps.plugins.kotlin.kapt) alias(deps.plugins.hilt.android) alias(deps.plugins.room) + alias(deps.plugins.ksp) id("configuration") } @@ -57,7 +58,7 @@ dependencies { implementation(deps.okHttp.prettyLogging) implementation(deps.retrofit) implementation(deps.retrofit.moshi) - kaptForObfuscatingVariants(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) kaptForObfuscatingVariants(deps.retrofit.response.type.keeper) /** Time */ @@ -78,7 +79,7 @@ dependencies { implementation(deps.androidx.datastore) implementation(deps.room.runtime) implementation(deps.room.ktx) - kapt(deps.room.compiler) + ksp(deps.room.compiler) testImplementation(deps.test.coroutine) testImplementation(deps.test.junit) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/visa/TangemVisaAuthProvider.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/visa/TangemVisaAuthProvider.kt deleted file mode 100644 index 1f164d8411..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/visa/TangemVisaAuthProvider.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tangem.datasource.api.common.visa - -interface TangemVisaAuthProvider { - - suspend fun getAuthHeader(cardId: String): String -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt index 043222d3e3..eaada836bf 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt @@ -2,6 +2,7 @@ package com.tangem.datasource.api.express.models.response import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.joda.time.DateTime @JsonClass(generateAdapter = true) data class ExchangeStatusResponse( @@ -26,6 +27,12 @@ data class ExchangeStatusResponse( @Json(name = "refundContractAddress") val refundContractAddress: String? = null, + + @Json(name = "createdAt") + val createdAt: DateTime? = null, + + @Json(name = "averageDuration") + val averageDuration: Int? = null, ) @JsonClass(generateAdapter = false) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt index 802738fbd7..a0d08a74a1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt @@ -5,9 +5,7 @@ import com.tangem.datasource.api.utils.ReadTimeout import com.tangem.datasource.api.visa.models.request.ActivationByCardWalletRequest import com.tangem.datasource.api.visa.models.request.ActivationByCustomerWalletRequest import com.tangem.datasource.api.visa.models.request.SetPinCodeRequest -import com.tangem.datasource.api.visa.models.response.CardActivationRemoteStateResponse -import com.tangem.datasource.api.visa.models.response.CardWalletDataToSignResponse -import com.tangem.datasource.api.visa.models.response.CustomerWalletDataToSignResponse +import com.tangem.datasource.api.visa.models.response.* import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Header @@ -71,4 +69,19 @@ interface TangemVisaApi { @Header("Authorization") authHeader: String, @Body body: SetPinCodeRequest, ): ApiResponse + + @GET("customer/info") + suspend fun getCustomerInfo( + @Header("Authorization") authHeader: String, + @Query("card_id") cardId: String, + ): ApiResponse + + @GET("product_instance/transactions") + suspend fun getTxHistory( + @Header("Authorization") authHeader: String, + @Query("customer_id") customerId: String, + @Query("product_instance_id") productInstanceId: String, + @Query("limit") limit: Int, + @Query("offset") offset: Int, + ): ApiResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaAuthApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaAuthApi.kt index 1e200c63bd..b2ed8d19be 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaAuthApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaAuthApi.kt @@ -1,5 +1,6 @@ package com.tangem.datasource.api.visa +import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.visa.models.response.GenerateNonceResponse import com.tangem.datasource.api.visa.models.response.JWTResponse import retrofit2.http.Field @@ -27,5 +28,5 @@ interface TangemVisaAuthApi { ): JWTResponse @POST("auth/refresh_token") - suspend fun refreshAccessToken(@Field("refresh_token") refreshToken: String): JWTResponse + suspend fun refreshAccessToken(@Field("refresh_token") refreshToken: String): ApiResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/VisaCustomerInfo.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/VisaCustomerInfo.kt new file mode 100644 index 0000000000..70d5f6ac94 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/VisaCustomerInfo.kt @@ -0,0 +1,15 @@ +package com.tangem.datasource.api.visa.models.response + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class VisaCustomerInfo( + @Json(name = "payment_accounts") val paymentAccounts: List, +) { + data class PaymentAccount( + @Json(name = "id") val id: String, + @Json(name = "customer_wallet_address") val customerWalletAddress: String, + @Json(name = "payment_account_address") val paymentAccountAddress: String, + ) +} \ No newline at end of file diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/model/VisaTxHistoryResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/VisaTxHistoryResponse.kt similarity index 98% rename from libs/visa/src/main/kotlin/com/tangem/lib/visa/model/VisaTxHistoryResponse.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/VisaTxHistoryResponse.kt index bf722ca7bf..7c4d7d0a22 100644 --- a/libs/visa/src/main/kotlin/com/tangem/lib/visa/model/VisaTxHistoryResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/VisaTxHistoryResponse.kt @@ -1,4 +1,4 @@ -package com.tangem.lib.visa.model +package com.tangem.datasource.api.visa.models.response import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/ExpressAssetsStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/ExpressAssetsStoreModule.kt index cf51b2b006..0799c40b41 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/ExpressAssetsStoreModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/ExpressAssetsStoreModule.kt @@ -1,12 +1,23 @@ package com.tangem.datasource.di +import android.content.Context +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.dataStoreFile +import com.squareup.moshi.Moshi +import com.tangem.datasource.api.express.models.response.Asset import com.tangem.datasource.local.datastore.RuntimeDataStore import com.tangem.datasource.local.token.DefaultExpressAssetsStore import com.tangem.datasource.local.token.ExpressAssetsStore +import com.tangem.datasource.utils.MoshiDataStoreSerializer +import com.tangem.datasource.utils.mapWithStringKeyTypes +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob import javax.inject.Singleton @Module @@ -15,7 +26,22 @@ internal object ExpressAssetsStoreModule { @Provides @Singleton - fun provideExpressAssetsStore(): ExpressAssetsStore { - return DefaultExpressAssetsStore(dataStore = RuntimeDataStore()) + fun provideExpressAssetsStore( + @NetworkMoshi moshi: Moshi, + @ApplicationContext context: Context, + dispatchers: CoroutineDispatcherProvider, + ): ExpressAssetsStore { + return DefaultExpressAssetsStore( + persistenceStore = DataStoreFactory.create( + serializer = MoshiDataStoreSerializer( + moshi = moshi, + types = mapWithStringKeyTypes(), + defaultValue = emptyMap(), + ), + produceFile = { context.dataStoreFile("express_assets") }, + scope = CoroutineScope(context = dispatchers.io + SupervisorJob()), + ), + runtimeStore = RuntimeDataStore(), + ) } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultExpressServiceLoader.kt b/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultExpressServiceLoader.kt index c2818ecd87..b966a12e0e 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultExpressServiceLoader.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultExpressServiceLoader.kt @@ -12,7 +12,9 @@ import com.tangem.domain.core.utils.lceError import com.tangem.domain.core.utils.lceLoading import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.update import kotlinx.coroutines.withContext import timber.log.Timber @@ -41,8 +43,6 @@ internal class DefaultExpressServiceLoader @Inject constructor( withContext(dispatchers.io) { val initializationStatus = getInitializationStatusInternal(userWalletId) - initializationStatus.update { lceLoading() } - try { if (userTokens.isNotEmpty()) { val response = tangemExpressApi.getAssets( @@ -54,22 +54,24 @@ internal class DefaultExpressServiceLoader @Inject constructor( initializationStatus.update { response.lceContent() } } } catch (e: Throwable) { - initializationStatus.update { e.lceError() } + if (expressAssetsStore.getSyncOrNull(userWalletId) == null) { + initializationStatus.update { e.lceError() } + } Timber.e(e, "Unable to fetch assets for: ${userWalletId.stringValue}") } } } - override fun getInitializationStatus(userWalletId: UserWalletId): InitializationStatusFlow { - return getInitializationStatusInternal(userWalletId) + override fun getInitializationStatus(userWalletId: UserWalletId): Flow>> { + return flow { getInitializationStatusInternal(userWalletId).collect { emit(it) } } } - private fun getInitializationStatusInternal(userWalletId: UserWalletId): InitializationStatusFlow { + private suspend fun getInitializationStatusInternal(userWalletId: UserWalletId): InitializationStatusFlow { val initializationStatus = initializationStatuses.value.get(key = userWalletId) - if (initializationStatus != null) return initializationStatus - val default: InitializationStatusFlow = MutableStateFlow(value = lceLoading()) + val cached = expressAssetsStore.getSyncOrNull(userWalletId) + val default: InitializationStatusFlow = MutableStateFlow(value = cached?.lceContent() ?: lceLoading()) initializationStatuses.update { it.toMutableMap().apply { diff --git a/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/ExpressServiceLoader.kt b/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/ExpressServiceLoader.kt index 72e59dba3a..f73ee82700 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/ExpressServiceLoader.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/ExpressServiceLoader.kt @@ -4,7 +4,7 @@ import com.tangem.datasource.api.express.models.request.LeastTokenInfo import com.tangem.datasource.api.express.models.response.Asset import com.tangem.domain.core.lce.Lce import com.tangem.domain.wallets.models.UserWalletId -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.Flow /** * Express service loader @@ -17,5 +17,5 @@ interface ExpressServiceLoader { suspend fun update(userWalletId: UserWalletId, userTokens: List) /** Get initialization status by [userWalletId] */ - fun getInitializationStatus(userWalletId: UserWalletId): StateFlow>> + fun getInitializationStatus(userWalletId: UserWalletId): Flow>> } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 434d4c4a92..50275788dd 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -121,6 +121,8 @@ object PreferencesKeys { val SEED_FIRST_NOTIFICATION_SHOW_TIME by lazy { longPreferencesKey("seedFirstNotificationTime") } + val WALLETS_NFT_ENABLED_STATES_KEY by lazy { stringPreferencesKey(name = "walletsNftEnabledStates") } + fun getShouldShowStoriesKey(storyId: String) = booleanPreferencesKey("shouldShowStories_$storyId") // region Permission diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultExpressAssetsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultExpressAssetsStore.kt index c67abc06e5..5d941ccc5f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultExpressAssetsStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultExpressAssetsStore.kt @@ -1,18 +1,53 @@ package com.tangem.datasource.local.token +import androidx.datastore.core.DataStore import com.tangem.datasource.api.express.models.response.Asset import com.tangem.datasource.local.datastore.core.StringKeyDataStore import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch + +internal typealias AssetsByWalletId = Map> internal class DefaultExpressAssetsStore( - private val dataStore: StringKeyDataStore>, + private val persistenceStore: DataStore, + private val runtimeStore: StringKeyDataStore>, ) : ExpressAssetsStore { override suspend fun getSyncOrNull(userWalletId: UserWalletId): List? { - return dataStore.getSyncOrNull(userWalletId.stringValue) + val runtimeAssets = runtimeStore.getSyncOrNull(userWalletId.stringValue) + + if (runtimeAssets != null) { + return runtimeAssets + } + + val cachedAssets = getCachedAssets(userWalletId) + + return if (cachedAssets != null) { + runtimeStore.store(userWalletId.stringValue, cachedAssets) + cachedAssets + } else { + null + } + } + + private suspend fun getCachedAssets(userWalletId: UserWalletId): List? { + return persistenceStore.data.firstOrNull().orEmpty()[userWalletId.stringValue] } override suspend fun store(userWalletId: UserWalletId, item: List) { - dataStore.store(userWalletId.stringValue, item) + coroutineScope { + launch { + runtimeStore.store(userWalletId.stringValue, item) + } + launch { + persistenceStore.updateData { + it.toMutableMap().apply { + put(userWalletId.stringValue, item) + } + } + } + } } } \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/DefaultAppComponentContext.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/DefaultAppComponentContext.kt index d4453d3e6a..ea9afa04a5 100644 --- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/DefaultAppComponentContext.kt +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/DefaultAppComponentContext.kt @@ -2,7 +2,7 @@ package com.tangem.core.decompose.context import com.arkivanov.decompose.ComponentContext import com.arkivanov.essenty.instancekeeper.getOrCreate -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.navigation.AppNavigationProvider import com.tangem.core.decompose.navigation.DefaultAppNavigationProvider import com.tangem.core.decompose.navigation.DefaultRouter @@ -15,7 +15,7 @@ import kotlinx.coroutines.CoroutineScope class DefaultAppComponentContext( componentContext: ComponentContext, override val dispatchers: CoroutineDispatcherProvider, - override val hiltComponentBuilder: DecomposeComponent.Builder, + override val hiltComponentBuilder: ModelComponent.Builder, override val messageSender: UiMessageSender, private val replaceRouter: Router? = null, ) : AppComponentContext, ComponentContext by componentContext { diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/ComponentScoped.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/ComponentScoped.kt deleted file mode 100644 index b5fde3b58f..0000000000 --- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/ComponentScoped.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.core.decompose.di - -/** - * Annotation for marking a dependency as a component scoped. - * - * This means that the lifecycle of the dependency is limited to the lifecycle of the component it is attached to. - */ -@Retention(AnnotationRetention.SOURCE) -annotation class ComponentScoped \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/HiltComponentBuilderOwner.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/HiltComponentBuilderOwner.kt index d210c58019..99067c8ec4 100644 --- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/HiltComponentBuilderOwner.kt +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/HiltComponentBuilderOwner.kt @@ -8,5 +8,5 @@ interface HiltComponentBuilderOwner { /** * Provides access to the Hilt component builder instance. */ - val hiltComponentBuilder: DecomposeComponent.Builder + val hiltComponentBuilder: ModelComponent.Builder } \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/DecomposeComponent.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/ModelComponent.kt similarity index 88% rename from core/decompose/src/main/kotlin/com/tangem/core/decompose/di/DecomposeComponent.kt rename to core/decompose/src/main/kotlin/com/tangem/core/decompose/di/ModelComponent.kt index 74fba5a48e..6a94065ebf 100644 --- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/DecomposeComponent.kt +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/ModelComponent.kt @@ -10,11 +10,11 @@ import dagger.hilt.components.SingletonComponent /** * Interface for the Decompose component. * - * It is annotated as [ComponentScoped], meaning it has a lifecycle that is scoped to the component. + * It is annotated as [ModelScoped], meaning it has a lifecycle that is scoped to the component. */ -@ComponentScoped +@ModelScoped @DefineComponent(parent = SingletonComponent::class) -interface DecomposeComponent { +interface ModelComponent { /** * Builder interface for the component. @@ -51,6 +51,6 @@ interface DecomposeComponent { * * @return The built Decompose component. */ - fun build(): DecomposeComponent + fun build(): ModelComponent } } \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/ModelScoped.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/ModelScoped.kt new file mode 100644 index 0000000000..ad4308f3d1 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/ModelScoped.kt @@ -0,0 +1,12 @@ +package com.tangem.core.decompose.di + +import javax.inject.Scope + +/** + * Annotation for marking a dependency as a model scoped. + * + * This means that the lifecycle of the dependency is limited to the lifecycle of the component's model + */ +@Scope +@Retention(AnnotationRetention.RUNTIME) +annotation class ModelScoped \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/Model.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/Model.kt index b1337c72f9..79ce712b57 100644 --- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/Model.kt +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/Model.kt @@ -6,6 +6,7 @@ import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.* import kotlinx.coroutines.flow.* +import javax.annotation.OverridingMethodsMustInvokeSuper /** * Abstract class for a component's model. @@ -29,6 +30,7 @@ abstract class Model : InstanceKeeper.Instance { CoroutineScope(context = dispatchers.mainImmediate + SupervisorJob()) } + @OverridingMethodsMustInvokeSuper override fun onDestroy() { runCatching { modelScope.cancel() } } diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ModelEntryPoint.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ModelEntryPoint.kt index 56b718e970..329556fff3 100644 --- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ModelEntryPoint.kt +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ModelEntryPoint.kt @@ -3,7 +3,9 @@ package com.tangem.core.decompose.model import com.arkivanov.essenty.instancekeeper.getOrCreate import com.arkivanov.essenty.instancekeeper.getOrCreateSimple import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.navigation.Router +import com.tangem.core.decompose.ui.UiMessageSender import dagger.hilt.EntryPoint import dagger.hilt.EntryPoints import dagger.hilt.InstallIn @@ -15,7 +17,7 @@ import javax.inject.Provider * It provides a map of model providers. */ @EntryPoint -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) interface ModelsEntryPoint { fun models(): Map, Provider> @@ -35,11 +37,15 @@ inline fun AppComponentContext.getOrCreateModel(): M = getOr * @param params The parameters to store in the [ParamsContainer], */ -inline fun AppComponentContext.getOrCreateModel(params: P?): M { - val entryPoint = instanceKeeper.getOrCreateSimple(key = "modelsEntryPoint") { +inline fun AppComponentContext.getOrCreateModel( + params: P?, + messageSender: UiMessageSender? = null, + router: Router? = null, +): M { + val entryPoint = instanceKeeper.getOrCreateSimple(key = "modelsEntryPoint_${M::class.simpleName}") { val hiltComponent = hiltComponentBuilder - .router(router) - .uiMessageSender(messageSender) + .router(router ?: this@getOrCreateModel.router) + .uiMessageSender(messageSender ?: this@getOrCreateModel.messageSender) .paramsContainer(MutableParamsContainer(params ?: Unit)) .build() diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ParamsContainer.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ParamsContainer.kt index 910764d661..4ab544bf1b 100644 --- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ParamsContainer.kt +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ParamsContainer.kt @@ -3,7 +3,7 @@ package com.tangem.core.decompose.model /** * Lazy container for [Model] params. * - * This contrainer can be accessed by DI because it's provided via [com.tangem.core.decompose.di.DecomposeComponent]. + * This contrainer can be accessed by DI because it's provided via [com.tangem.core.decompose.di.ModelComponent]. * */ interface ParamsContainer { diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt index d2d4fb95e7..ed5cb7be3f 100644 --- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt @@ -24,7 +24,7 @@ internal class DefaultRouter( navigation.navigate( transformer = { newRoutes }, - onComplete = { newStack, _ -> onComplete(newStack.size == newRoutes.size) }, + onComplete = { newStack, _ -> newStack == newRoutes }, ) } diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/inner/InnerPopRouter.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/inner/InnerPopRouter.kt new file mode 100644 index 0000000000..12242d09b7 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/inner/InnerPopRouter.kt @@ -0,0 +1,36 @@ +package com.tangem.core.decompose.navigation.inner + +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.navigation.Route +import com.tangem.core.decompose.navigation.Router +import kotlin.reflect.KClass + +/** + * Creates an router for overriding the pop operation. + * + * @param popCallback The callback to pop the route. + */ +@Suppress("FunctionName") +inline fun AppComponentContext.InnerPopRouter( + crossinline popCallback: ((onComplete: (Boolean) -> Unit) -> Unit), +): Router = object : Router { + override fun push(route: Route, onComplete: (Boolean) -> Unit) { + router.push(route, onComplete) + } + + override fun replaceAll(vararg routes: Route, onComplete: (Boolean) -> Unit) { + router.replaceAll(*routes, onComplete = onComplete) + } + + override fun pop(onComplete: (Boolean) -> Unit) { + popCallback(onComplete) + } + + override fun popTo(route: Route, onComplete: (Boolean) -> Unit) { + router.popTo(route, onComplete) + } + + override fun popTo(routeClass: KClass, onComplete: (Boolean) -> Unit) { + router.popTo(routeClass, onComplete) + } +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/inner/InnerRouter.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/inner/InnerRouter.kt new file mode 100644 index 0000000000..788afecd42 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/inner/InnerRouter.kt @@ -0,0 +1,81 @@ +package com.tangem.core.decompose.navigation.inner + +import com.arkivanov.decompose.router.stack.StackNavigation +import com.arkivanov.decompose.router.stack.pop +import com.arkivanov.decompose.router.stack.pushNew +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.navigation.Route +import com.tangem.core.decompose.navigation.Router +import kotlin.reflect.KClass + +/** + * Creates a router that can handle a specific type of route. + * + * @param stackNavigation The stack navigation instance. + * @param popCallback The callback to pop the route. + * @param fallBackRouter The fallback router. + * @return The router. + */ +@Suppress("FunctionName") +inline fun AppComponentContext.InnerRouter( + stackNavigation: StackNavigation, + fallBackRouter: Router = router, + noinline popCallback: ((onComplete: (Boolean) -> Unit) -> Unit) = + { onComplete -> stackNavigation.pop(onComplete) }, +): Router = object : Router { + + override fun push(route: Route, onComplete: (Boolean) -> Unit) { + if (route is T) { + stackNavigation.pushNew(route, onComplete) + } else { + fallBackRouter.push(route, onComplete) + } + } + + override fun replaceAll(vararg routes: Route, onComplete: (Boolean) -> Unit) { + if (routes.any { it is T }) { + val newRoutes = routes.toList().filterIsInstance() + + stackNavigation.navigate( + transformer = { newRoutes }, + onComplete = { newStack, _ -> onComplete(newStack == newRoutes) }, + ) + } else { + fallBackRouter.replaceAll(*routes, onComplete = onComplete) + } + } + + override fun pop(onComplete: (Boolean) -> Unit) { + popCallback(onComplete) + } + + override fun popTo(route: Route, onComplete: (Boolean) -> Unit) { + if (route is T) { + stackNavigation.navigate( + transformer = { stack -> + stack + .dropLastWhile { it != route } + .ifEmpty { stack } + }, + onComplete = { newStack, oldStack -> onComplete(newStack.size < oldStack.size) }, + ) + } else { + fallBackRouter.popTo(route, onComplete) + } + } + + override fun popTo(routeClass: KClass, onComplete: (Boolean) -> Unit) { + if (routeClass == T::class) { + stackNavigation.navigate( + transformer = { stack -> + stack + .dropLastWhile { it::class != routeClass } + .ifEmpty { stack } + }, + onComplete = { newStack, oldStack -> onComplete(newStack.size < oldStack.size) }, + ) + } else { + fallBackRouter.popTo(routeClass, onComplete) + } + } +} \ No newline at end of file diff --git a/core/deep-links/build.gradle.kts b/core/deep-links/build.gradle.kts index 9d43ab04df..c086d8f8d6 100644 --- a/core/deep-links/build.gradle.kts +++ b/core/deep-links/build.gradle.kts @@ -10,6 +10,8 @@ android { } dependencies { + /* Core */ + implementation(projects.core.decompose) /* Libs - AndroidX */ implementation(deps.lifecycle.runtime.ktx) diff --git a/core/deep-links/src/main/kotlin/com/tangem/core/deeplink/DeepLinksRegistry.kt b/core/deep-links/src/main/kotlin/com/tangem/core/deeplink/DeepLinksRegistry.kt index 105ddbc940..fc56ae10a0 100644 --- a/core/deep-links/src/main/kotlin/com/tangem/core/deeplink/DeepLinksRegistry.kt +++ b/core/deep-links/src/main/kotlin/com/tangem/core/deeplink/DeepLinksRegistry.kt @@ -1,8 +1,6 @@ package com.tangem.core.deeplink import android.content.Intent -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.ViewModel // TODO: Add tests /** @@ -21,17 +19,11 @@ interface DeepLinksRegistry { /** * Registers the given [deepLink]. - * - * @see registerWithLifecycle - * @see registerWithViewModel */ fun register(deepLink: DeepLink) /** * Registers the given [deepLinks]. - * - * @see registerWithLifecycle - * @see registerWithViewModel */ fun register(deepLinks: Collection) @@ -50,17 +42,6 @@ interface DeepLinksRegistry { * */ fun unregisterByIds(ids: Collection) - /** - * Registers the [deepLinks] when the [owner] is resumed and ensures that they are unregistered when the [owner] is - * stopped. - */ - fun registerWithLifecycle(owner: LifecycleOwner, deepLinks: Collection) - - /** - * Registers the [deepLinks] and ensures that they are unregistered when the [ViewModel] is closed. - */ - fun registerWithViewModel(viewModel: ViewModel, deepLinks: Collection) - /** * Triggers run last launched [Intent] with deeplink handlers that can handle delayed deeplink * after handle [Intent] clear that and second time no intent will be handled diff --git a/core/deep-links/src/main/kotlin/com/tangem/core/deeplink/impl/DefaultDeepLinksRegistry.kt b/core/deep-links/src/main/kotlin/com/tangem/core/deeplink/impl/DefaultDeepLinksRegistry.kt index e51f6b8b37..63f6f60d78 100644 --- a/core/deep-links/src/main/kotlin/com/tangem/core/deeplink/impl/DefaultDeepLinksRegistry.kt +++ b/core/deep-links/src/main/kotlin/com/tangem/core/deeplink/impl/DefaultDeepLinksRegistry.kt @@ -3,11 +3,8 @@ package com.tangem.core.deeplink.impl import android.content.Intent import android.net.Uri import androidx.core.net.toUri -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.ViewModel import com.tangem.core.deeplink.DeepLink import com.tangem.core.deeplink.DeepLinksRegistry -import com.tangem.core.deeplink.utils.DeepLinksLifecycleObserver import timber.log.Timber internal class DefaultDeepLinksRegistry : DeepLinksRegistry { @@ -103,19 +100,6 @@ internal class DefaultDeepLinksRegistry : DeepLinksRegistry { ) } - override fun registerWithLifecycle(owner: LifecycleOwner, deepLinks: Collection) { - val observer = DeepLinksLifecycleObserver(deepLinksRegistry = this, deepLinks) - owner.lifecycle.addObserver(observer) - } - - override fun registerWithViewModel(viewModel: ViewModel, deepLinks: Collection) { - viewModel.addCloseable { - unregister(deepLinks) - } - - register(deepLinks) - } - override fun triggerDelayedDeeplink() { if (lastIntent != null) { val intent = lastIntent diff --git a/core/deep-links/src/main/kotlin/com/tangem/core/deeplink/utils/AppComponentContextExtensions.kt b/core/deep-links/src/main/kotlin/com/tangem/core/deeplink/utils/AppComponentContextExtensions.kt new file mode 100644 index 0000000000..594b8305ca --- /dev/null +++ b/core/deep-links/src/main/kotlin/com/tangem/core/deeplink/utils/AppComponentContextExtensions.kt @@ -0,0 +1,21 @@ +package com.tangem.core.deeplink.utils + +import com.arkivanov.essenty.lifecycle.subscribe +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.deeplink.DeepLink +import com.tangem.core.deeplink.DeepLinksRegistry + +fun AppComponentContext.registerDeepLinks(registry: DeepLinksRegistry, vararg deepLinks: DeepLink) { + registerDeepLinks(registry, deepLinks.toList()) +} + +fun AppComponentContext.registerDeepLinks(registry: DeepLinksRegistry, deepLinks: Collection) { + lifecycle.subscribe( + onCreate = { + registry.register(deepLinks) + }, + onDestroy = { + registry.unregister(deepLinks) + }, + ) +} \ No newline at end of file diff --git a/core/deep-links/src/main/kotlin/com/tangem/core/deeplink/utils/DeepLinksLifecycleObserver.kt b/core/deep-links/src/main/kotlin/com/tangem/core/deeplink/utils/DeepLinksLifecycleObserver.kt deleted file mode 100644 index ae1580e17e..0000000000 --- a/core/deep-links/src/main/kotlin/com/tangem/core/deeplink/utils/DeepLinksLifecycleObserver.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.core.deeplink.utils - -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner -import com.tangem.core.deeplink.DeepLink -import com.tangem.core.deeplink.DeepLinksRegistry - -internal class DeepLinksLifecycleObserver( - private val deepLinksRegistry: DeepLinksRegistry, - private val deepLinks: Collection, -) : DefaultLifecycleObserver { - - override fun onResume(owner: LifecycleOwner) { - deepLinksRegistry.register(deepLinks) - } - - override fun onPause(owner: LifecycleOwner) { - deepLinksRegistry.unregister(deepLinks) - } -} \ No newline at end of file diff --git a/core/pagination/src/main/java/com/tangem/pagination/BatchListSource.kt b/core/pagination/src/main/java/com/tangem/pagination/BatchListSource.kt index 535c7deea4..dab020caa2 100644 --- a/core/pagination/src/main/java/com/tangem/pagination/BatchListSource.kt +++ b/core/pagination/src/main/java/com/tangem/pagination/BatchListSource.kt @@ -289,7 +289,7 @@ private class DefaultBatchListSource batchFetcher.fetchNext(action.requestParams, lastResult) }.getOrElse { BatchFetchResult.Error(it) } - lastRequestResult.value = lastResult + lastRequestResult.value = res state.update { currentState -> when (res) { diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index f4e57a45b5..65eaf3453a 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -495,6 +495,10 @@ Pull this up or tap the search bar to add tokens directly from the market Add tokens NFC is not available on your device + NFT collections + Tap here to receive first NFT + %1$d NFTs in %2$d collection + Unable to load the data Set up a single access code to protect all your devices. Protect Set an individual access code for each card or ring later. diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/CurrencyPlaceholderIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/CurrencyPlaceholderIcon.kt index 71b0550226..0cfef1c3d7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/CurrencyPlaceholderIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/CurrencyPlaceholderIcon.kt @@ -5,7 +5,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.Text +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/MiddleEllipsisText.kt b/core/ui/src/main/java/com/tangem/core/ui/components/MiddleEllipsisText.kt index e67fbf3de2..892ef05493 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/MiddleEllipsisText.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/MiddleEllipsisText.kt @@ -1,7 +1,7 @@ package com.tangem.core.ui.components -import androidx.compose.material.LocalTextStyle -import androidx.compose.material.Text +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Notifier.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Notifier.kt index 5e3e78a06a..ad18ba92f0 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Notifier.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Notifier.kt @@ -5,7 +5,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding -import androidx.compose.material.Text +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/ResizableText.kt b/core/ui/src/main/java/com/tangem/core/ui/components/ResizableText.kt index 649e5c7788..f5d84c141b 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/ResizableText.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/ResizableText.kt @@ -2,8 +2,8 @@ package com.tangem.core.ui.components import androidx.annotation.FloatRange import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material.LocalTextStyle -import androidx.compose.material.Text +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent @@ -27,13 +27,13 @@ fun ResizableText( maxLines: Int = Int.MAX_VALUE, style: TextStyle = LocalTextStyle.current, ) { - val fontSizeValue = remember { mutableStateOf(fontSizeRange.max.value) } + val fontSizeValue = remember { mutableFloatStateOf(fontSizeRange.max.value) } val readyToDraw = remember { mutableStateOf(false) } val textState = remember { mutableStateOf(text) } if (textState.value != text) { readyToDraw.value = false - fontSizeValue.value = fontSizeRange.max.value + fontSizeValue.floatValue = fontSizeRange.max.value textState.value = text } @@ -41,18 +41,18 @@ fun ResizableText( text = text, modifier = modifier.drawWithContent { if (readyToDraw.value) drawContent() }, color = color, - fontSize = fontSizeValue.value.sp, + fontSize = fontSizeValue.floatValue.sp, overflow = overflow, softWrap = false, maxLines = maxLines, onTextLayout = { if (it.hasVisualOverflow) { - val nextFontSizeValue = fontSizeValue.value - fontSizeRange.step.value + val nextFontSizeValue = fontSizeValue.floatValue - fontSizeRange.step.value if (nextFontSizeValue <= fontSizeRange.min.value) { - fontSizeValue.value = fontSizeRange.min.value + fontSizeValue.floatValue = fontSizeRange.min.value readyToDraw.value = true } else { - fontSizeValue.value = nextFontSizeValue * COEFFICIENT + fontSizeValue.floatValue = nextFontSizeValue * COEFFICIENT } } else { readyToDraw.value = true diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/SettingsRow.kt b/core/ui/src/main/java/com/tangem/core/ui/components/SettingsRow.kt index cb5266dce9..b1b8fa87b0 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/SettingsRow.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/SettingsRow.kt @@ -5,8 +5,8 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* -import androidx.compose.material.Icon -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt index dbe2a76c91..2c8c37e80b 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt @@ -14,7 +14,8 @@ import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.* +import androidx.compose.foundation.text.selection.LocalTextSelectionColors +import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -119,7 +120,7 @@ private fun TangemTextField( keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardActions: KeyboardActions = KeyboardActions(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - colors: TangemTextFieldColors = TangemTextFieldsDefault.defaultTextFieldColors, + colors: TextFieldColors = TangemTextFieldsDefault.defaultTextFieldColors, size: TangemTextFieldSize = TangemTextFieldSize.Default, onClear: () -> Unit = { onValueChange(TextFieldValue()) }, ) { @@ -230,7 +231,7 @@ private fun TangemTextFieldWithIcon( keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardActions: KeyboardActions = KeyboardActions(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - colors: TangemTextFieldColors = TangemTextFieldsDefault.defaultTextFieldColors, + colors: TextFieldColors = TangemTextFieldsDefault.defaultTextFieldColors, size: TangemTextFieldSize = TangemTextFieldSize.Default, onIconClick: () -> Unit = {}, ) { @@ -326,154 +327,139 @@ private fun TangemTextFieldSize.toShape(): Shape = when (this) { } object TangemTextFieldsDefault { - val defaultTextFieldColors: TangemTextFieldColors - @Composable @Stable get() = TangemTextFieldColors( - textColor = TangemTheme.colors.text.primary1, + val defaultTextFieldColors: TextFieldColors + @Composable @Stable get() = TextFieldColors( + focusedTextColor = TangemTheme.colors.text.primary1, + errorTextColor = TangemTheme.colors.text.primary1, + unfocusedTextColor = TangemTheme.colors.text.primary1, disabledTextColor = TangemTheme.colors.text.disabled, - backgroundColor = Color.Transparent, + focusedContainerColor = Color.Transparent, + unfocusedContainerColor = Color.Transparent, + disabledContainerColor = Color.Transparent, + errorContainerColor = Color.Transparent, cursorColor = TangemTheme.colors.icon.primary1, errorCursorColor = TangemTheme.colors.icon.warning, focusedIndicatorColor = TangemTheme.colors.icon.primary1, unfocusedIndicatorColor = TangemTheme.colors.stroke.secondary, disabledIndicatorColor = TangemTheme.colors.stroke.secondary, errorIndicatorColor = TangemTheme.colors.icon.warning, - leadingIconColor = TangemTheme.colors.icon.informative, + focusedLeadingIconColor = TangemTheme.colors.icon.informative, + unfocusedLeadingIconColor = TangemTheme.colors.icon.informative, disabledLeadingIconColor = Color.Transparent, errorLeadingIconColor = TangemTheme.colors.icon.warning, - trailingIconColor = TangemTheme.colors.icon.informative, + focusedTrailingIconColor = TangemTheme.colors.icon.informative, + unfocusedTrailingIconColor = TangemTheme.colors.icon.informative, disabledTrailingIconColor = Color.Transparent, errorTrailingIconColor = TangemTheme.colors.icon.warning, focusedLabelColor = TangemTheme.colors.text.primary1, unfocusedLabelColor = TangemTheme.colors.text.secondary, disabledLabelColor = TangemTheme.colors.text.disabled, errorLabelColor = TangemTheme.colors.icon.warning, - placeholderColor = TangemTheme.colors.text.secondary, + focusedPlaceholderColor = TangemTheme.colors.text.secondary, + errorPlaceholderColor = TangemTheme.colors.text.secondary, + unfocusedPlaceholderColor = TangemTheme.colors.text.secondary, disabledPlaceholderColor = TangemTheme.colors.text.disabled, - captionColor = TangemTheme.colors.text.tertiary, - disabledCaptionColor = TangemTheme.colors.text.disabled, - errorCaptionColor = TangemTheme.colors.icon.warning, + focusedSupportingTextColor = TangemTheme.colors.text.tertiary, + unfocusedSupportingTextColor = TangemTheme.colors.text.tertiary, + disabledSupportingTextColor = TangemTheme.colors.text.disabled, + errorSupportingTextColor = TangemTheme.colors.icon.warning, + textSelectionColors = LocalTextSelectionColors.current, + focusedPrefixColor = Color.Transparent, + unfocusedPrefixColor = Color.Transparent, + disabledPrefixColor = Color.Transparent, + errorPrefixColor = Color.Transparent, + focusedSuffixColor = Color.Transparent, + unfocusedSuffixColor = Color.Transparent, + disabledSuffixColor = Color.Transparent, + errorSuffixColor = Color.Transparent, ) } -@Immutable -data class TangemTextFieldColors( - private val textColor: Color, - private val disabledTextColor: Color, - private val cursorColor: Color, - private val errorCursorColor: Color, - private val focusedIndicatorColor: Color, - private val unfocusedIndicatorColor: Color, - private val errorIndicatorColor: Color, - private val disabledIndicatorColor: Color, - private val leadingIconColor: Color, - private val disabledLeadingIconColor: Color, - private val errorLeadingIconColor: Color, - private val trailingIconColor: Color, - private val disabledTrailingIconColor: Color, - private val errorTrailingIconColor: Color, - private val backgroundColor: Color, - private val focusedLabelColor: Color, - private val unfocusedLabelColor: Color, - private val disabledLabelColor: Color, - private val errorLabelColor: Color, - private val placeholderColor: Color, - private val disabledPlaceholderColor: Color, - private val captionColor: Color, - private val disabledCaptionColor: Color, - private val errorCaptionColor: Color, -) : TextFieldColors { +@Composable +fun TextFieldColors.leadingIconColor(enabled: Boolean, isError: Boolean): State { + return rememberUpdatedState( + when { + !enabled -> disabledLeadingIconColor + isError -> errorLeadingIconColor + else -> focusedLeadingIconColor + }, + ) +} - @Composable - override fun leadingIconColor(enabled: Boolean, isError: Boolean): State { - return rememberUpdatedState( - when { - !enabled -> disabledLeadingIconColor - isError -> errorLeadingIconColor - else -> leadingIconColor - }, +@Composable +fun TextFieldColors.trailingIconColor(enabled: Boolean, isError: Boolean): State { + return rememberUpdatedState( + when { + !enabled -> disabledTrailingIconColor + isError -> errorTrailingIconColor + else -> focusedTrailingIconColor + }, + ) +} + +@Composable +fun TextFieldColors.indicatorColor( + enabled: Boolean, + isError: Boolean, + interactionSource: InteractionSource, +): State { + val focused by interactionSource.collectIsFocusedAsState() + + val targetValue = when { + !enabled -> disabledIndicatorColor + isError -> errorIndicatorColor + focused -> focusedIndicatorColor + else -> unfocusedIndicatorColor + } + return if (enabled) { + animateColorAsState( + targetValue = targetValue, + animationSpec = tween(durationMillis = 120), + label = "IndicatorColor", ) + } else { + rememberUpdatedState(targetValue) } +} - @Composable - override fun trailingIconColor(enabled: Boolean, isError: Boolean): State { - return rememberUpdatedState( - when { - !enabled -> disabledTrailingIconColor - isError -> errorTrailingIconColor - else -> trailingIconColor - }, - ) +@Composable +fun TextFieldColors.placeholderColor(enabled: Boolean): State { + return rememberUpdatedState(if (enabled) focusedPlaceholderColor else disabledPlaceholderColor) +} + +@Composable +fun TextFieldColors.labelColor(enabled: Boolean, error: Boolean, interactionSource: InteractionSource): State { + val focused by interactionSource.collectIsFocusedAsState() + + val targetValue = when { + !enabled -> disabledLabelColor + error -> errorLabelColor + focused -> focusedLabelColor + else -> unfocusedLabelColor } + return rememberUpdatedState(targetValue) +} - @Composable - override fun indicatorColor( - enabled: Boolean, - isError: Boolean, - interactionSource: InteractionSource, - ): State { - val focused by interactionSource.collectIsFocusedAsState() +@Composable +fun TextFieldColors.textColor(enabled: Boolean): State { + return rememberUpdatedState(if (enabled) focusedTextColor else disabledTextColor) +} - val targetValue = when { - !enabled -> disabledIndicatorColor - isError -> errorIndicatorColor - focused -> focusedIndicatorColor - else -> unfocusedIndicatorColor - } - return if (enabled) { - animateColorAsState( - targetValue = targetValue, - animationSpec = tween(durationMillis = 120), - label = "IndicatorColor", - ) - } else { - rememberUpdatedState(targetValue) - } - } +@Composable +fun TextFieldColors.cursorColor(isError: Boolean): State { + return rememberUpdatedState(if (isError) errorCursorColor else cursorColor) +} - @Composable - override fun backgroundColor(enabled: Boolean): State { - return rememberUpdatedState(backgroundColor) - } - - @Composable - override fun placeholderColor(enabled: Boolean): State { - return rememberUpdatedState(if (enabled) placeholderColor else disabledPlaceholderColor) - } - - @Composable - override fun labelColor(enabled: Boolean, error: Boolean, interactionSource: InteractionSource): State { - val focused by interactionSource.collectIsFocusedAsState() - - val targetValue = when { - !enabled -> disabledLabelColor - error -> errorLabelColor - focused -> focusedLabelColor - else -> unfocusedLabelColor - } - return rememberUpdatedState(targetValue) - } - - @Composable - override fun textColor(enabled: Boolean): State { - return rememberUpdatedState(if (enabled) textColor else disabledTextColor) - } - - @Composable - override fun cursorColor(isError: Boolean): State { - return rememberUpdatedState(if (isError) errorCursorColor else cursorColor) - } - - @Suppress("TopLevelComposableFunctions") - @Composable - fun captionColor(enabled: Boolean, isError: Boolean): State { - return rememberUpdatedState( - newValue = when { - !enabled -> disabledCaptionColor - isError -> errorCaptionColor - else -> captionColor - }, - ) - } +@Suppress("TopLevelComposableFunctions") +@Composable +fun TextFieldColors.captionColor(enabled: Boolean, isError: Boolean): State { + return rememberUpdatedState( + newValue = when { + !enabled -> disabledSupportingTextColor + isError -> errorSupportingTextColor + else -> focusedSupportingTextColor + }, + ) } // endregion Defaults diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt index ab6996b21d..b9c76c147c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt @@ -7,7 +7,10 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.* +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material3.TextField import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -105,7 +108,7 @@ private fun CollapsedSearchView( .padding(TangemTheme.dimens.spacing16) .size(TangemTheme.dimens.size24) .clickable { onBackClick() }, - tint = MaterialTheme.colors.onPrimary, + tint = TangemTheme.colors.icon.primary1, ) Column( verticalArrangement = Arrangement.Center, @@ -216,7 +219,8 @@ private fun ExpandedSearchView( onDone = { focusManager.clearFocus() }, ), colors = TangemTextFieldsDefault.defaultTextFieldColors.copy( - placeholderColor = TangemTheme.colors.text.disabled, + focusedPlaceholderColor = TangemTheme.colors.text.disabled, + unfocusedPlaceholderColor = TangemTheme.colors.text.disabled, cursorColor = TangemTheme.colors.text.tertiary, focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/audits/AuditLabel.kt b/core/ui/src/main/java/com/tangem/core/ui/components/audits/AuditLabel.kt index 8cc27144be..eed177c971 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/audits/AuditLabel.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/audits/AuditLabel.kt @@ -3,7 +3,7 @@ package com.tangem.core.ui.components.audits import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding -import androidx.compose.material.Text +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.Modifier diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleDialogTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleDialogTextField.kt index 098f576a94..197385d9d9 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleDialogTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleDialogTextField.kt @@ -8,7 +8,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.BasicTextField -import androidx.compose.material.Text +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/rows/ActionRow.kt b/core/ui/src/main/java/com/tangem/core/ui/components/rows/ActionRow.kt index 36c44d81b3..2f66450aba 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/rows/ActionRow.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/rows/ActionRow.kt @@ -3,8 +3,8 @@ package com.tangem.core.ui.components.rows import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.material.Icon -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt index 8c7bdbc6d9..761db501ac 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt @@ -445,5 +445,6 @@ private class TransactionItemStateProvider : CollectionPreviewParameterProvider< onClick = {}, ), TransactionState.Loading(txHash = UUID.randomUUID().toString()), + TransactionState.Locked(txHash = UUID.randomUUID().toString()), ), ) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionList.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionList.kt index 7aeca782bf..5ff2f3c7bc 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionList.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionList.kt @@ -104,7 +104,7 @@ private fun LazyListScope.contentItems( } @Composable -private fun PendingTxsBlock(pendingTxs: ImmutableList, isBalanceHidden: Boolean) { +fun PendingTxsBlock(pendingTxs: ImmutableList, isBalanceHidden: Boolean) { Column( modifier = Modifier .padding(top = TangemTheme.dimens.spacing12) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryTitle.kt index 4f8201f07c..f7cb3263ce 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryTitle.kt @@ -23,7 +23,7 @@ import com.tangem.core.ui.res.TangemThemePreview * @param modifier modifier */ @Composable -internal fun TxHistoryTitle(onExploreClick: () -> Unit, modifier: Modifier = Modifier) { +fun TxHistoryTitle(onExploreClick: () -> Unit, modifier: Modifier = Modifier) { Row( modifier = modifier .background(TangemTheme.colors.background.primary) diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt index f3a42975bc..44c9a4dd6d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt @@ -90,6 +90,9 @@ fun getActiveIconRes(blockchainId: String): Int { "bitrock", "bitrock/test" -> R.drawable.img_bitrock_22 "sonic", "sonic/test" -> R.drawable.img_sonic_22 "apechain", "apechain/test" -> R.drawable.img_apecoin_22 + "scroll", "scroll/test" -> R.drawable.ic_alert_24 // FIXME: add icon during full integration + "zklink", "zklink/test" -> R.drawable.img_zklink_22 + "vanar-chain", "vanar-chain/test" -> R.drawable.img_vanar_22 else -> R.drawable.ic_alert_24 } } @@ -178,6 +181,9 @@ fun getActiveIconResByCoinId(coinId: String): Int { "bitrock", "bitrock/test" -> R.drawable.img_bitrock_22 "sonic", "sonic/test" -> R.drawable.img_sonic_22 "apechain", "apechain/test" -> R.drawable.img_apecoin_22 + "scroll", "scroll/test" -> R.drawable.ic_alert_24 // FIXME: add icon during full integration + "zklink", "zklink/test" -> R.drawable.img_zklink_22 + "vanar-chain", "vanar-chain/test" -> R.drawable.img_vanar_22 else -> R.drawable.ic_alert_24 } } @@ -269,6 +275,9 @@ fun getGreyedOutIconRes(blockchainId: String): Int { "bitrock", "bitrock/test" -> R.drawable.ic_bitrock_22 "sonic", "sonic/test" -> R.drawable.ic_sonic_22 "apechain", "apechain/test" -> R.drawable.ic_apecoin_22 + "scroll", "scroll/test" -> R.drawable.ic_alert_24 // FIXME: add icon during full integration + "zklink", "zklink/test" -> R.drawable.ic_zklink_22 + "vanar-chain", "vanar-chain/test" -> R.drawable.ic_vanar_22 else -> R.drawable.ic_alert_24 } } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_fingerprint_24.xml b/core/ui/src/main/res/drawable/ic_fingerprint_24.xml new file mode 100644 index 0000000000..df5bdf4868 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_fingerprint_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_nft_empty_36.xml b/core/ui/src/main/res/drawable/ic_nft_empty_36.xml new file mode 100644 index 0000000000..b246aaf6e1 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_nft_empty_36.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core/ui/src/main/res/drawable/ic_nft_preview_more_16.xml b/core/ui/src/main/res/drawable/ic_nft_preview_more_16.xml new file mode 100644 index 0000000000..e7a9118384 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_nft_preview_more_16.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/core/ui/src/main/res/drawable/ic_trash_24.xml b/core/ui/src/main/res/drawable/ic_trash_24.xml new file mode 100644 index 0000000000..eb3f278341 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_trash_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_vanar_22.xml b/core/ui/src/main/res/drawable/ic_vanar_22.xml new file mode 100644 index 0000000000..3ddb0cea23 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_vanar_22.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_zklink_22.xml b/core/ui/src/main/res/drawable/ic_zklink_22.xml new file mode 100644 index 0000000000..70fb36416d --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_zklink_22.xml @@ -0,0 +1,14 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_vanar_22.xml b/core/ui/src/main/res/drawable/img_vanar_22.xml new file mode 100644 index 0000000000..7fc4405bcb --- /dev/null +++ b/core/ui/src/main/res/drawable/img_vanar_22.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/core/ui/src/main/res/drawable/img_zklink_22.xml b/core/ui/src/main/res/drawable/img_zklink_22.xml new file mode 100644 index 0000000000..91405830fc --- /dev/null +++ b/core/ui/src/main/res/drawable/img_zklink_22.xml @@ -0,0 +1,24 @@ + + + + + + + + + diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt index d0b50ceab4..901cf071d6 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt @@ -42,4 +42,16 @@ class Debouncer { companion object { const val DEFAULT_WAIT_TIME_MS = 500L } +} + +fun CoroutineScope.launchOnCancellation(block: suspend () -> Unit) { + launch { + try { + awaitCancellation() + } finally { + withContext(NonCancellable) { + block() + } + } + } } \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/di/CoroutineDispatcherProviderModule.kt b/core/utils/src/main/java/com/tangem/utils/di/CoroutineDispatcherProviderModule.kt index 6dd1011462..cf0847f2f0 100644 --- a/core/utils/src/main/java/com/tangem/utils/di/CoroutineDispatcherProviderModule.kt +++ b/core/utils/src/main/java/com/tangem/utils/di/CoroutineDispatcherProviderModule.kt @@ -9,7 +9,7 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -interface CoroutineDispatcherProviderModule { +internal interface CoroutineDispatcherProviderModule { @Binds fun bindCoroutineDispatcherProvider( diff --git a/data/balance-hiding/build.gradle.kts b/data/balance-hiding/build.gradle.kts index 84db19b2d7..3d3caf3397 100644 --- a/data/balance-hiding/build.gradle.kts +++ b/data/balance-hiding/build.gradle.kts @@ -12,6 +12,7 @@ android { dependencies { implementation(deps.androidx.datastore) + implementation(deps.androidx.appCompat) /** DI */ implementation(deps.hilt.android) diff --git a/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/DefaultBalanceHidingRepository.kt b/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/DefaultBalanceHidingRepository.kt index f2fdcd320e..b0200df3ff 100644 --- a/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/DefaultBalanceHidingRepository.kt +++ b/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/DefaultBalanceHidingRepository.kt @@ -8,8 +8,11 @@ import com.tangem.datasource.local.preferences.utils.storeObject import com.tangem.domain.balancehiding.BalanceHidingSettings import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository import kotlinx.coroutines.flow.Flow +import javax.inject.Inject +import javax.inject.Singleton -internal class DefaultBalanceHidingRepository( +@Singleton +internal class DefaultBalanceHidingRepository @Inject constructor( private val appPreferencesStore: AppPreferencesStore, ) : BalanceHidingRepository { diff --git a/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/DefaultDeviceFlipDetector.kt b/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/DefaultDeviceFlipDetector.kt index 30b2e97726..9a4c8de638 100644 --- a/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/DefaultDeviceFlipDetector.kt +++ b/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/DefaultDeviceFlipDetector.kt @@ -3,18 +3,40 @@ package com.tangem.data.balancehiding import android.content.Context import android.hardware.Sensor import android.hardware.SensorManager +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner import com.tangem.domain.balancehiding.DeviceFlipDetector +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import java.util.concurrent.atomic.AtomicBoolean +import javax.inject.Inject +import javax.inject.Singleton -internal class DefaultDeviceFlipDetector(context: Context) : DeviceFlipDetector { +@Singleton +class DefaultDeviceFlipDetector @Inject constructor( + @ApplicationContext context: Context, +) : DeviceFlipDetector, DefaultLifecycleObserver { private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager private var gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY) + private var isResumedState = AtomicBoolean(false) + + override fun onPause(owner: LifecycleOwner) { + isResumedState.set(false) + } + + override fun onResume(owner: LifecycleOwner) { + isResumedState.set(true) + } override fun getDeviceFlipFlow(): Flow = callbackFlow { - val listener = FlipListener { trySend(Unit) } + val listener = FlipListener { + if (isResumedState.get()) { + trySend(Unit) + } + } gravitySensor?.let { sensorManager.registerListener(listener, it, SensorManager.SENSOR_DELAY_NORMAL) diff --git a/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/di/BalanceHidingModule.kt b/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/di/BalanceHidingModule.kt index f83fac4932..0f213c83f8 100644 --- a/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/di/BalanceHidingModule.kt +++ b/data/balance-hiding/src/main/java/com/tangem/data/balancehiding/di/BalanceHidingModule.kt @@ -1,31 +1,24 @@ package com.tangem.data.balancehiding.di -import android.content.Context import com.tangem.data.balancehiding.DefaultBalanceHidingRepository import com.tangem.data.balancehiding.DefaultDeviceFlipDetector -import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.balancehiding.DeviceFlipDetector import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository +import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -internal object BalanceHidingModule { +internal interface BalanceHidingModule { - @Provides + @Binds @Singleton - fun provideBalanceHidingRepository(appPreferencesStore: AppPreferencesStore): BalanceHidingRepository { - return DefaultBalanceHidingRepository(appPreferencesStore = appPreferencesStore) - } + fun provideBalanceHidingRepository(impl: DefaultBalanceHidingRepository): BalanceHidingRepository - @Provides + @Binds @Singleton - fun provideFlipDetector(@ApplicationContext context: Context): DeviceFlipDetector { - return DefaultDeviceFlipDetector(context = context) - } + fun provideFlipDetector(impl: DefaultDeviceFlipDetector): DeviceFlipDetector } \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/NetworkOperations.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/NetworkOperations.kt index 31d90fac3f..a0fde030cc 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/NetworkOperations.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/NetworkOperations.kt @@ -299,6 +299,8 @@ private fun Blockchain.getSupportedTransactionExtras(): Network.TransactionExtra Blockchain.Bitrock, Blockchain.BitrockTestnet, Blockchain.Sonic, Blockchain.SonicTestnet, Blockchain.ApeChain, Blockchain.ApeChainTestnet, + Blockchain.Scroll, Blockchain.ScrollTestnet, + Blockchain.ZkLinkNova, Blockchain.ZkLinkNovaTestnet, -> Network.TransactionExtrasType.NONE // endregion } diff --git a/data/manage-tokens/build.gradle.kts b/data/manage-tokens/build.gradle.kts index d303cffc3f..f0e57105f8 100644 --- a/data/manage-tokens/build.gradle.kts +++ b/data/manage-tokens/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(deps.plugins.android.library) alias(deps.plugins.kotlin.android) alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.ksp) id("configuration") } @@ -46,6 +47,6 @@ dependencies { /** Other */ implementation(deps.moshi.kotlin) implementation(deps.timber) - kaptForObfuscatingVariants(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) kaptForObfuscatingVariants(deps.retrofit.response.type.keeper) } \ No newline at end of file diff --git a/data/markets/build.gradle.kts b/data/markets/build.gradle.kts index 9f3c3fc995..fa746e853f 100644 --- a/data/markets/build.gradle.kts +++ b/data/markets/build.gradle.kts @@ -5,6 +5,7 @@ plugins { alias(deps.plugins.kotlin.android) alias(deps.plugins.kotlin.kapt) alias(deps.plugins.hilt.android) + alias(deps.plugins.ksp) id("configuration") } @@ -42,7 +43,7 @@ dependencies { implementation(deps.moshi.kotlin) implementation(deps.timber) implementation(tangemDeps.blockchain) - kaptForObfuscatingVariants(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) kaptForObfuscatingVariants(deps.retrofit.response.type.keeper) // endregion } diff --git a/data/onramp/build.gradle.kts b/data/onramp/build.gradle.kts index fc4b8695b8..01a57f9c01 100644 --- a/data/onramp/build.gradle.kts +++ b/data/onramp/build.gradle.kts @@ -5,6 +5,7 @@ plugins { alias(deps.plugins.kotlin.android) alias(deps.plugins.kotlin.kapt) alias(deps.plugins.hilt.android) + alias(deps.plugins.ksp) id("configuration") } @@ -44,7 +45,7 @@ dependencies { implementation(deps.moshi) implementation(deps.moshi.kotlin) implementation(deps.timber) - kaptForObfuscatingVariants(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) kaptForObfuscatingVariants(deps.retrofit.response.type.keeper) implementation(deps.kotlin.serialization) diff --git a/data/settings/build.gradle.kts b/data/settings/build.gradle.kts index 7997b79d5c..30c7231276 100644 --- a/data/settings/build.gradle.kts +++ b/data/settings/build.gradle.kts @@ -5,6 +5,7 @@ plugins { alias(deps.plugins.kotlin.android) alias(deps.plugins.kotlin.kapt) alias(deps.plugins.hilt.android) + alias(deps.plugins.ksp) id("configuration") } @@ -33,7 +34,7 @@ dependencies { implementation(deps.moshi) implementation(deps.moshi.kotlin) implementation(deps.timber) - kaptForObfuscatingVariants(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) kaptForObfuscatingVariants(deps.retrofit.response.type.keeper) // endregion } diff --git a/data/staking/build.gradle.kts b/data/staking/build.gradle.kts index 040b80e3d7..9af26ac76e 100644 --- a/data/staking/build.gradle.kts +++ b/data/staking/build.gradle.kts @@ -5,6 +5,7 @@ plugins { alias(deps.plugins.kotlin.android) alias(deps.plugins.kotlin.kapt) alias(deps.plugins.hilt.android) + alias(deps.plugins.ksp) id("configuration") } @@ -49,7 +50,7 @@ dependencies { implementation(deps.moshi.kotlin) implementation(deps.timber) implementation(deps.firebase.crashlytics) - kaptForObfuscatingVariants(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) kaptForObfuscatingVariants(deps.retrofit.response.type.keeper) implementation(projects.libs.blockchainSdk) diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index 876e516350..d08938d3fe 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -36,6 +36,7 @@ import com.tangem.datasource.local.token.StakingBalanceStore import com.tangem.datasource.local.token.StakingYieldsStore import com.tangem.datasource.local.token.converter.StakingNetworkTypeConverter import com.tangem.datasource.local.token.converter.TokenConverter +import com.tangem.domain.common.TapWorkarounds.isWallet2 import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo @@ -57,6 +58,7 @@ import com.tangem.domain.tokens.model.Network import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.lib.crypto.BlockchainUtils.isCardano import com.tangem.lib.crypto.BlockchainUtils.isSolana import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.extensions.orZero @@ -247,6 +249,7 @@ internal class DefaultStakingRepository( private fun checkFeatureToggleEnabled(networkId: Network.ID): Boolean { return when (Blockchain.fromId(networkId.value)) { Blockchain.TON -> stakingFeatureToggles.isTonStakingEnabled + Blockchain.Cardano -> stakingFeatureToggles.isCardanoStakingEnabled else -> true } } @@ -255,14 +258,11 @@ internal class DefaultStakingRepository( val userWallet = getUserWalletUseCase(userWalletId).getOrElse { error("Failed to get user wallet") } - + val blockchainId = cryptoCurrency.network.id.value return when { - isSolana(cryptoCurrency.network.id.value) -> { - INVALID_BATCHES_FOR_SOLANA.contains(userWallet.scanResponse.card.batchId) - } - else -> { - false - } + isSolana(blockchainId) -> INVALID_BATCHES_FOR_SOLANA.contains(userWallet.scanResponse.card.batchId) + isCardano(blockchainId) -> !userWallet.scanResponse.card.isWallet2 + else -> false } } @@ -273,7 +273,7 @@ internal class DefaultStakingRepository( ): StakingAction { return withContext(dispatchers.io) { val response = when (params.actionCommonType) { - StakingActionCommonType.Enter -> stakeKitApi.createEnterAction( + is StakingActionCommonType.Enter -> stakeKitApi.createEnterAction( createActionRequestBody( userWalletId, network, @@ -303,7 +303,7 @@ internal class DefaultStakingRepository( ): StakingGasEstimate { return withContext(dispatchers.io) { val gasEstimateDTO = when (params.actionCommonType) { - StakingActionCommonType.Enter -> stakeKitApi.estimateGasOnEnter( + is StakingActionCommonType.Enter -> stakeKitApi.estimateGasOnEnter( createActionRequestBody( userWalletId, network, @@ -674,13 +674,14 @@ internal class DefaultStakingRepository( -> TransactionData.Compiled.Data.Bytes(unsignedTransaction.hexToBytes()) Blockchain.BSC, Blockchain.Ethereum, + Blockchain.TON, + Blockchain.Cardano, -> TransactionData.Compiled.Data.RawString(unsignedTransaction) Blockchain.Tron -> { val tronStakeKitTransaction = tronStakeKitTransactionAdapter.fromJson(unsignedTransaction) ?: error("Failed to parse Tron StakeKit transaction") TransactionData.Compiled.Data.RawString(tronStakeKitTransaction.rawDataHex) } - Blockchain.TON -> TransactionData.Compiled.Data.RawString(unsignedTransaction) else -> error("Unsupported blockchain") } } @@ -749,6 +750,7 @@ internal class DefaultStakingRepository( const val KAVA_INTEGRATION_ID = "kava-kava-native-staking" const val NEAR_INTEGRATION_ID = "near-near-native-staking" const val TEZOS_INTEGRATION_ID = "tezos-xtz-native-staking" + const val CARDANO_INTEGRATION_ID = "cardano-ada-native-staking" const val ETHEREUM_POLYGON_APPROVE_SPENDER = "0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908" @@ -771,6 +773,7 @@ internal class DefaultStakingRepository( // Blockchain.Kava.run { id + toCoinId() } to KAVA_INTEGRATION_ID, // Blockchain.Near.run { id + toCoinId() } to NEAR_INTEGRATION_ID, // Blockchain.Tezos.run { id + toCoinId() } to TEZOS_INTEGRATION_ID, + Blockchain.Cardano.run { id + toCoinId() } to CARDANO_INTEGRATION_ID, ) } } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/toggles/DefaultStakingFeatureToggles.kt b/data/staking/src/main/java/com/tangem/data/staking/toggles/DefaultStakingFeatureToggles.kt index 3b3259c021..a0c9e1640c 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/toggles/DefaultStakingFeatureToggles.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/toggles/DefaultStakingFeatureToggles.kt @@ -9,4 +9,7 @@ internal class DefaultStakingFeatureToggles( override val isTonStakingEnabled: Boolean get() = featureTogglesManager.isFeatureEnabled(name = "STAKING_TON_ENABLED") + + override val isCardanoStakingEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled("STAKING_CARDANO_ENABLED") } \ No newline at end of file diff --git a/data/tokens/build.gradle.kts b/data/tokens/build.gradle.kts index 003cc83849..499154d785 100644 --- a/data/tokens/build.gradle.kts +++ b/data/tokens/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(deps.plugins.android.library) alias(deps.plugins.kotlin.android) alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.ksp) id("configuration") } @@ -50,6 +51,6 @@ dependencies { implementation(deps.timber) implementation(deps.retrofit) // For HttpException implementation(deps.androidx.paging.runtime) - kaptForObfuscatingVariants(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) kaptForObfuscatingVariants(deps.retrofit.response.type.keeper) } \ No newline at end of file diff --git a/data/txhistory/build.gradle.kts b/data/txhistory/build.gradle.kts index 23bb1bd73d..0a6a9baf74 100644 --- a/data/txhistory/build.gradle.kts +++ b/data/txhistory/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { implementation(projects.core.utils) implementation(projects.core.datasource) + implementation(projects.core.pagination) implementation(projects.domain.legacy) implementation(projects.libs.blockchainSdk) implementation(projects.domain.tokens.models) diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/di/TxHistoryDataModule.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/di/TxHistoryDataModule.kt index ebc92a5036..1a756f54e1 100644 --- a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/di/TxHistoryDataModule.kt +++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/di/TxHistoryDataModule.kt @@ -2,9 +2,11 @@ package com.tangem.data.txhistory.di import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.txhistory.repository.DefaultTxHistoryRepository +import com.tangem.data.txhistory.repository.RefactoredTxHistoryRepository import com.tangem.datasource.local.txhistory.TxHistoryItemsStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.txhistory.repository.TxHistoryRepository +import com.tangem.domain.txhistory.repository.TxHistoryRepositoryV2 import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -32,4 +34,18 @@ internal object TxHistoryDataModule { txHistoryItemsStore, dispatchers, ) + + @Provides + @Singleton + fun provideTxHistoryRepositoryV2( + walletManagersFacade: WalletManagersFacade, + dispatchers: CoroutineDispatcherProvider, + txHistoryItemsStore: TxHistoryItemsStore, + cacheRegistry: CacheRegistry, + ): TxHistoryRepositoryV2 = RefactoredTxHistoryRepository( + walletManagersFacade = walletManagersFacade, + dispatchers = dispatchers, + txHistoryItemsStore = txHistoryItemsStore, + cacheRegistry = cacheRegistry, + ) } \ No newline at end of file diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/RefactoredTxHistoryRepository.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/RefactoredTxHistoryRepository.kt new file mode 100644 index 0000000000..97c64d5f55 --- /dev/null +++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/RefactoredTxHistoryRepository.kt @@ -0,0 +1,126 @@ +package com.tangem.data.txhistory.repository + +import com.tangem.data.common.cache.CacheRegistry +import com.tangem.data.txhistory.repository.paging.TxHistoryPageBatchFetcher +import com.tangem.datasource.local.txhistory.TxHistoryItemsStore +import com.tangem.domain.txhistory.model.TxHistoryListBatchFlow +import com.tangem.domain.txhistory.model.TxHistoryListBatchingContext +import com.tangem.domain.txhistory.model.TxHistoryListConfig +import com.tangem.domain.txhistory.models.Page +import com.tangem.domain.txhistory.models.PaginationWrapper +import com.tangem.domain.txhistory.models.TxHistoryItem +import com.tangem.domain.txhistory.repository.TxHistoryRepositoryV2 +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.walletmanager.utils.SdkPageConverter +import com.tangem.pagination.BatchFetchResult +import com.tangem.pagination.BatchListSource +import com.tangem.pagination.toBatchFlow +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import timber.log.Timber + +internal class RefactoredTxHistoryRepository( + private val walletManagersFacade: WalletManagersFacade, + private val txHistoryItemsStore: TxHistoryItemsStore, + private val cacheRegistry: CacheRegistry, + private val dispatchers: CoroutineDispatcherProvider, +) : TxHistoryRepositoryV2 { + + private val sdkPageConverter = SdkPageConverter() + private val TxHistoryListConfig.storeKey get() = TxHistoryItemsStore.Key(userWalletId, currency) + + override fun getTxHistoryBatchFlow(batchSize: Int, context: TxHistoryListBatchingContext): TxHistoryListBatchFlow { + return BatchListSource( + fetchDispatcher = dispatchers.io, + context = context, + generateNewKey = { keys -> keys.lastOrNull()?.inc() ?: 0 }, + batchFetcher = createFetcher(batchSize), + ).toBatchFlow() + } + + private fun createFetcher( + batchSize: Int, + ): TxHistoryPageBatchFetcher> = + TxHistoryPageBatchFetcher { request, _ -> + val wrappedItems = loadItems(request, batchSize) + BatchFetchResult.Success( + data = wrappedItems, + empty = wrappedItems.items.isEmpty(), + last = wrappedItems.nextPage is Page.LastPage, + ) + } + + private suspend fun loadItems( + request: TxHistoryPageBatchFetcher.Request, + batchSize: Int, + ): PaginationWrapper { + cacheRegistry.invokeOnExpire( + key = getTxHistoryPageKey(request.page, request.params), + skipCache = request.params.refresh, + block = { fetch(request, batchSize) }, + ) + + return txHistoryItemsStore.getSync(request.page, request.params) + } + + private suspend fun fetch(request: TxHistoryPageBatchFetcher.Request, batchSize: Int) { + val wrappedItems = walletManagersFacade.getTxHistoryItems( + userWalletId = request.params.userWalletId, + currency = request.params.currency, + page = sdkPageConverter.convertBack(request.page), + pageSize = batchSize, + ) + + txHistoryItemsStore.store(key = request.params.storeKey, value = wrappedItems) + } + + private suspend fun TxHistoryItemsStore.getSync( + pageToLoad: Page, + config: TxHistoryListConfig, + ): PaginationWrapper { + val storedItems = requireNotNull(getSyncOrNull(config.storeKey, pageToLoad)) { + "The transaction history page #$pageToLoad could not be retrieved" + } + + return if (pageToLoad is Page.Initial) storedItems.addRecentTransactions(config) else storedItems + } + + private suspend fun PaginationWrapper.addRecentTransactions( + config: TxHistoryListConfig, + ): PaginationWrapper { + val recentItems = walletManagersFacade.getRecentTransactions( + userWalletId = config.userWalletId, + currency = config.currency, + ) + .filterUnconfirmedTransaction() + .sortedByDescending { it.timestampInMillis } + .filterIfTxAlreadyAdded(apiItems = items) + + return if (recentItems.isEmpty()) { + Timber.d("Nothing to add to TxHistory") + this + } else { + Timber.d( + "Recent transactions were added to TxHistory: %s", + recentItems.joinToString( + prefix = "[", + postfix = "]", + transform = TxHistoryItem::txHash, + ), + ) + + return copy(items = recentItems + items) + } + } + + private fun List.filterUnconfirmedTransaction(): List { + return filter { it.status == TxHistoryItem.TransactionStatus.Unconfirmed } + } + + private fun List.filterIfTxAlreadyAdded(apiItems: List): List { + return filter { item -> apiItems.none { it.txHash == item.txHash } } + } + + private fun getTxHistoryPageKey(page: Page, config: TxHistoryListConfig): String { + return "tx_history_page_${config.currency}_${config.userWalletId}_$page" + } +} \ No newline at end of file diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/paging/TxHistoryPageBatchFetcher.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/paging/TxHistoryPageBatchFetcher.kt new file mode 100644 index 0000000000..429f0a6f30 --- /dev/null +++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/paging/TxHistoryPageBatchFetcher.kt @@ -0,0 +1,73 @@ +package com.tangem.data.txhistory.repository.paging + +import com.tangem.domain.txhistory.models.Page +import com.tangem.domain.txhistory.models.PaginationWrapper +import com.tangem.domain.txhistory.models.TxHistoryItem +import com.tangem.pagination.BatchFetchResult +import com.tangem.pagination.exception.EndOfPaginationException +import com.tangem.pagination.fetcher.BatchFetcher +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.flow.MutableStateFlow + +internal class TxHistoryPageBatchFetcher>( + private val subFetcher: SubFetcher, +) : BatchFetcher { + data class Request(val page: Page, val params: TRequestParams) + fun interface SubFetcher { + suspend fun fetch( + request: Request, + lastResult: BatchFetchResult?, + ): BatchFetchResult + } + + private val lastRequest = MutableStateFlow?>(null) + + override suspend fun fetchFirst(requestParams: TRequestParams): BatchFetchResult { + val req = Request( + page = Page.Initial, + params = requestParams, + ) + + val res = runCatching { + subFetcher.fetch(request = req, lastResult = null) + }.getOrElse { + currentCoroutineContext().ensureActive() + BatchFetchResult.Error(it) + } + + lastRequest.value = req + return res + } + + override suspend fun fetchNext( + overrideRequestParams: TRequestParams?, + lastResult: BatchFetchResult, + ): BatchFetchResult { + val last = lastRequest.value + requireNotNull(last) + + val req = if (lastResult is BatchFetchResult.Success) { + if (lastResult.last && overrideRequestParams == null) { + return BatchFetchResult.Error(EndOfPaginationException()) + } + + Request( + page = lastResult.data.nextPage, + params = overrideRequestParams ?: last.params, + ) + } else { + last + } + + val res = runCatching { + subFetcher.fetch(request = req, lastResult = lastResult) + }.getOrElse { + currentCoroutineContext().ensureActive() + BatchFetchResult.Error(it) + } + + lastRequest.value = req + return res + } +} \ No newline at end of file diff --git a/data/visa/build.gradle.kts b/data/visa/build.gradle.kts index b2f273b514..035040383b 100644 --- a/data/visa/build.gradle.kts +++ b/data/visa/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(deps.plugins.android.library) alias(deps.plugins.kotlin.android) alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.ksp) id("configuration") } @@ -29,7 +30,7 @@ dependencies { implementation(projects.libs.blockchainSdk) /** Project - Libs */ - debugImplementation(projects.libs.visa) + implementation(projects.libs.visa) /** Libs - Other */ implementation(deps.kotlin.coroutines) @@ -39,7 +40,7 @@ dependencies { implementation(deps.timber) implementation(deps.androidx.paging.runtime) implementation(deps.moshi.kotlin) - kaptForObfuscatingVariants(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) kaptForObfuscatingVariants(deps.retrofit.response.type.keeper) /** Libs - Tangem */ diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/di/ImplementedVisaDataModule.kt b/data/visa/src/debug/kotlin/com/tangem/data/visa/di/ImplementedVisaDataModule.kt deleted file mode 100644 index efb081bdc6..0000000000 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/di/ImplementedVisaDataModule.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.data.visa.di - -import com.tangem.data.common.cache.CacheRegistry -import com.tangem.data.visa.DefaultVisaRepository -import com.tangem.data.visa.config.VisaLibLoader -import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.visa.repository.VisaRepository -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.Binds -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal interface ImplementedVisaDataModule { - - @Binds - @Singleton - @ImplementedVisaRepository - fun provideVisaRepository(impl: DefaultVisaRepository): VisaRepository -} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt index 4bb73eee50..b4bb976ce9 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt @@ -1,5 +1,6 @@ package com.tangem.data.visa +import com.tangem.data.visa.config.VisaLibLoader import com.tangem.data.visa.converter.AccessCodeDataConverter import com.tangem.data.visa.converter.VisaActivationStatusConverter import com.tangem.datasource.api.common.response.ApiResponseError @@ -28,6 +29,7 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor( private val visaAuthTokenStorage: VisaAuthTokenStorage, private val accessCodeDataConverter: AccessCodeDataConverter, private val visaAuthRepository: VisaAuthRepository, + private val visaLibLoader: VisaLibLoader, ) : VisaActivationRepository { override suspend fun getActivationRemoteState(): VisaActivationRemoteState = withContext(dispatcherProvider.io) { @@ -192,6 +194,10 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor( } } + override suspend fun getPinCodeRsaEncryptionPublicKey(): String { + return visaLibLoader.getOrCreateConfig().rsaPublicKey + } + private suspend fun request(requestBlock: suspend () -> T): T { return runCatching { requestBlock() diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt similarity index 65% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt index b7ce28f265..6a78abbf06 100644 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt @@ -12,21 +12,18 @@ import com.tangem.common.extensions.toHexString import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.visa.config.VisaLibLoader import com.tangem.data.visa.utils.* -import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.getOrThrow -import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.visa.TangemVisaApi +import com.tangem.datasource.api.visa.models.response.VisaTxHistoryResponse import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.common.visa.VisaUtilities -import com.tangem.domain.visa.exception.RefreshTokenExpiredException -import com.tangem.domain.visa.model.* -import com.tangem.domain.visa.repository.VisaAuthRepository +import com.tangem.domain.visa.model.VisaCurrency +import com.tangem.domain.visa.model.VisaTxDetails +import com.tangem.domain.visa.model.VisaTxHistoryItem import com.tangem.domain.visa.repository.VisaRepository import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.lib.visa.api.VisaApi -import com.tangem.lib.visa.model.VisaTxHistoryResponse import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -35,8 +32,8 @@ import kotlinx.coroutines.withContext import java.math.BigDecimal import javax.inject.Inject import javax.inject.Singleton -import kotlin.jvm.Throws +@Suppress("LongParameterList") @Singleton internal class DefaultVisaRepository @Inject constructor( private val visaLibLoader: VisaLibLoader, @@ -44,8 +41,8 @@ internal class DefaultVisaRepository @Inject constructor( private val cacheRegistry: CacheRegistry, private val userWalletsStore: UserWalletsStore, private val dispatchers: CoroutineDispatcherProvider, - private val visaAuthProvider: TangemVisaAuthProvider, - private val visaAuthRepository: VisaAuthRepository, + private val visaApiRequestMaker: VisaApiRequestMaker, + private val visaApi: TangemVisaApi, ) : VisaRepository { private val currencyFactory by lazy(mode = LazyThreadSafetyMode.NONE) { @@ -65,27 +62,32 @@ internal class DefaultVisaRepository @Inject constructor( override suspend fun getVisaCurrency(userWalletId: UserWalletId, isRefresh: Boolean): VisaCurrency { val address = makeAddress(userWalletId) - fetchVisaCurrencyIfExpired(address, isRefresh) + fetchVisaCurrencyIfExpired(userWalletId, address, isRefresh) return requireNotNull(fetchedCurrencies.value[address]) { "Unable to find VISA currency for $address" } } - private suspend fun fetchVisaCurrencyIfExpired(address: String, isRefresh: Boolean) { + private suspend fun fetchVisaCurrencyIfExpired(userWalletId: UserWalletId, address: String, isRefresh: Boolean) { cacheRegistry.invokeOnExpire( key = getVisaCurrencyKey(address), skipCache = isRefresh, - block = { fetchVisaCurrency(address) }, + block = { fetchVisaCurrency(userWalletId, address) }, ) } - private suspend fun fetchVisaCurrency(address: String) { + private suspend fun fetchVisaCurrency(userWalletId: UserWalletId, address: String) { val contractInfoProvider = visaLibLoader.getOrCreateProvider() parZip( dispatchers.io, - { contractInfoProvider.getContractInfo(address) }, + { + contractInfoProvider.getContractInfo( + walletAddress = address, + paymentAccountAddress = getPaymentAccountAddress(userWalletId), + ) + }, { getFiatRate() }, { contractInfo, fiatRate -> fetchedCurrencies.update { value -> @@ -104,7 +106,7 @@ internal class DefaultVisaRepository @Inject constructor( ): Flow> { val userWallet = findVisaUserWallet(userWalletId) val cardPubKey = getCardPubKey(userWallet) - val api = visaLibLoader.getOrCreateApi() + val pager = Pager( config = PagingConfig( pageSize = pageSize, @@ -121,7 +123,7 @@ internal class DefaultVisaRepository @Inject constructor( cacheRegistry = cacheRegistry, fetchedItems = fetchedHistoryItems, dispatchers = dispatchers, - requestTxHistory = { offset, pageSize -> getTxHistory(api, userWalletId, offset, pageSize) }, + requestTxHistory = { offset, pageSize -> getTxHistory(userWalletId, offset, pageSize) }, ) }, ) @@ -145,22 +147,31 @@ internal class DefaultVisaRepository @Inject constructor( } } - private suspend fun getTxHistory( - api: VisaApi, - userWalletId: UserWalletId, - offset: Int, - pageSize: Int, - ): VisaTxHistoryResponse = withContext(dispatchers.io) { + private suspend fun getPaymentAccountAddress(userWalletId: UserWalletId): String? = runCatching { val userWallet = findVisaUserWallet(userWalletId) - val cardPubKey = getCardPubKey(userWallet) - request(userWalletId = userWalletId) { - api.getTxHistory( - authorizationHeader = visaAuthProvider.getAuthHeader(userWallet.cardId), - cardPublicKey = cardPubKey, + val customerInfo = visaApiRequestMaker.request(userWalletId) { authHeader, _ -> + visaApi.getCustomerInfo( + authHeader = authHeader, + cardId = userWallet.scanResponse.card.cardId, + ) + } + + // TODO select correct account when multiple accounts are available (will be implemented when backend is ready) + customerInfo.paymentAccounts.firstOrNull()?.paymentAccountAddress + }.getOrNull() + + private suspend fun getTxHistory(userWalletId: UserWalletId, offset: Int, pageSize: Int): VisaTxHistoryResponse { + return visaApiRequestMaker.request( + userWalletId = userWalletId, + ) { authHeader, accessCodeData -> + visaApi.getTxHistory( + authHeader = authHeader, + customerId = accessCodeData.customerId, + productInstanceId = accessCodeData.productInstanceId, limit = pageSize, offset = offset, - ).getOrThrow() + ) } } @@ -217,56 +228,4 @@ internal class DefaultVisaRepository @Inject constructor( private fun getVisaCurrencyKey(address: String): String { return "visa_currency_$address" } - - private suspend fun request( - userWalletId: UserWalletId, - requestBlock: suspend () -> T, - ): T { - return runCatching { - requestBlock() - }.getOrElse { responseError -> - if (responseError !is ApiResponseError.HttpException || - responseError.code != ApiResponseError.HttpException.Code.UNAUTHORIZED - ) { - throw responseError - } - - val authTokens = getAuthTokens(userWalletId) - val newTokens = runCatching { - visaAuthRepository.refreshAccessTokens(authTokens.refreshToken) - }.getOrElse { - if (it is ApiResponseError.HttpException && - it.code == ApiResponseError.HttpException.Code.UNAUTHORIZED - ) { - userWalletsStore.update(userWalletId) { userWallet -> - userWallet.copy( - scanResponse = userWallet.scanResponse.copy( - visaCardActivationStatus = VisaCardActivationStatus.RefreshTokenExpired - ) - ) - } - } - throw RefreshTokenExpiredException() - } - - userWalletsStore.update(userWalletId) { userWallet -> - userWallet.copy( - scanResponse = userWallet.scanResponse.copy( - visaCardActivationStatus = VisaCardActivationStatus.Activated( - visaAuthTokens = newTokens - ) - ) - ) - } - - requestBlock() - } - } - - @Throws - private suspend fun getAuthTokens(userWalletId: UserWalletId): VisaAuthTokens { - val userWallet = findVisaUserWallet(userWalletId) - val status = userWallet.scanResponse.visaCardActivationStatus ?: error("Visa card activation status not found") - return (status as? VisaCardActivationStatus.Activated)?.visaAuthTokens ?: error("Visa card is not activated") - } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaActivationRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaActivationRepository.kt index 8ebb4ae1b4..ed49e8187b 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaActivationRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaActivationRepository.kt @@ -45,6 +45,10 @@ class MockVisaActivationRepository @AssistedInject constructor( override suspend fun sendPinCode(pinCode: VisaEncryptedPinCode) {} + override suspend fun getPinCodeRsaEncryptionPublicKey(): String { + return CryptoUtils.generateRandomBytes(length = 32).toHexString() + } + @AssistedFactory interface Factory : VisaActivationRepository.Factory { override fun create(cardId: VisaCardId): MockVisaActivationRepository diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DummyVisaRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaRepository.kt similarity index 89% rename from data/visa/src/main/kotlin/com/tangem/data/visa/DummyVisaRepository.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaRepository.kt index e965d71e93..87402d0554 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DummyVisaRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaRepository.kt @@ -7,8 +7,9 @@ import com.tangem.domain.visa.model.VisaTxHistoryItem import com.tangem.domain.visa.repository.VisaRepository import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow +import javax.inject.Inject -internal class DummyVisaRepository : VisaRepository { +internal class MockVisaRepository @Inject constructor() : VisaRepository { override suspend fun getVisaCurrency(userWalletId: UserWalletId, isRefresh: Boolean): VisaCurrency { TODO("Not implemented for this build type") diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaConfig.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/config/VisaConfig.kt similarity index 92% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaConfig.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/config/VisaConfig.kt index 187f4ca9e6..2dd3b5566c 100644 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaConfig.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/config/VisaConfig.kt @@ -11,6 +11,8 @@ internal data class VisaConfig( val mainnet: Addresses, @Json(name = "txHistoryAPIAdditionalHeaders") val header: Header, + @Json(name = "rsaPublicKey") + val rsaPublicKey: String, ) { @JsonClass(generateAdapter = true) diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaLibLoader.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/config/VisaLibLoader.kt similarity index 65% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaLibLoader.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/config/VisaLibLoader.kt index e5062cf662..f8935ef774 100644 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaLibLoader.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/config/VisaLibLoader.kt @@ -1,13 +1,9 @@ package com.tangem.data.visa.config -import com.squareup.moshi.Moshi import com.tangem.data.visa.BuildConfig import com.tangem.data.visa.utils.VisaConstants import com.tangem.datasource.asset.loader.AssetLoader -import com.tangem.datasource.di.NetworkMoshi import com.tangem.lib.visa.VisaContractInfoProvider -import com.tangem.lib.visa.api.VisaApi -import com.tangem.lib.visa.api.VisaApiBuilder import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -15,22 +11,24 @@ import javax.inject.Inject internal class VisaLibLoader @Inject constructor( private val assetLoader: AssetLoader, - @NetworkMoshi private val moshi: Moshi, private val dispatchers: CoroutineDispatcherProvider, ) { - private val createMutex = Mutex() + private val createMutex2 = Mutex() + @Volatile private var config: VisaConfig? = null + @Volatile private var provider: VisaContractInfoProvider? = null - private var api: VisaApi? = null + + suspend fun getOrCreateConfig(): VisaConfig = config ?: getOrLoadConfig() suspend fun getOrCreateProvider(): VisaContractInfoProvider = provider ?: createProvider() - suspend fun getOrCreateApi(): VisaApi = api ?: createApi() - private suspend fun createProvider(): VisaContractInfoProvider = createMutex.withLock { + if (provider != null) return@withLock requireNotNull(provider) + val config = getOrLoadConfig() provider = VisaContractInfoProvider.Builder( @@ -54,33 +52,17 @@ internal class VisaLibLoader @Inject constructor( } } - private suspend fun createApi(): VisaApi = createMutex.withLock { - val config = getOrLoadConfig() + private suspend fun getOrLoadConfig(): VisaConfig = createMutex2.withLock { + if (config != null) return@withLock requireNotNull(config) - api = VisaApiBuilder( - useDevApi = VisaConstants.USE_TEST_ENV, - isNetworkLoggingEnabled = BuildConfig.LOG_ENABLED, - moshi = moshi, - headers = mapOf( - X_ASN_HEADER_NAME to config.header.xAsn, - ), - ).build() - - return requireNotNull(api) { - "Visa API is not created" - } - } - - private suspend fun getOrLoadConfig(): VisaConfig { config = assetLoader.load(VISA_CONFIG_FILE_NAME) - return requireNotNull(config) { - "Visa config is not found" + return@withLock requireNotNull(config) { + "Visa config is not loaded" } } companion object { private const val VISA_CONFIG_FILE_NAME = "tangem-app-config/visa_config" - private const val X_ASN_HEADER_NAME = "x-asn" } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/di/ImplementedVisaRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/di/ImplementedVisaRepository.kt deleted file mode 100644 index 074af6e72d..0000000000 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/di/ImplementedVisaRepository.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tangem.data.visa.di - -import javax.inject.Qualifier - -@Qualifier -internal annotation class ImplementedVisaRepository \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/di/ImplementedVisaRepositoryModule.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/di/ImplementedVisaRepositoryModule.kt deleted file mode 100644 index 96de38914e..0000000000 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/di/ImplementedVisaRepositoryModule.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.data.visa.di - -import com.tangem.domain.visa.repository.VisaRepository -import dagger.BindsOptionalOf -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent - -@Module -@InstallIn(SingletonComponent::class) -internal interface ImplementedVisaRepositoryModule { - - @BindsOptionalOf - @ImplementedVisaRepository - fun bindImplementedVisaRepository(): VisaRepository -} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt index 5cfa0296bb..e1cc459371 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt @@ -1,36 +1,20 @@ package com.tangem.data.visa.di import com.tangem.data.visa.DefaultVisaAuthRepository -import com.tangem.data.visa.DummyVisaRepository +import com.tangem.data.visa.MockVisaRepository import com.tangem.data.visa.MockVisaActivationRepository import com.tangem.domain.visa.repository.VisaActivationRepository import com.tangem.domain.visa.repository.VisaAuthRepository import com.tangem.domain.visa.repository.VisaRepository import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import java.util.Optional import javax.inject.Singleton -import kotlin.jvm.optionals.getOrNull @Module @InstallIn(SingletonComponent::class) -internal object VisaDataModule { - - @Provides - @Singleton - fun provideVisaRepository( - @ImplementedVisaRepository implementedVisaRepository: Optional, - ): VisaRepository { - return implementedVisaRepository.getOrNull() ?: DummyVisaRepository() - } -} - -@Module -@InstallIn(SingletonComponent::class) -internal interface VisaDataBindsModule { +internal interface VisaDataModule { @Binds @Singleton @@ -48,4 +32,11 @@ internal interface VisaDataBindsModule { fun bindVisaActivationRepositoryFactory( repository: MockVisaActivationRepository.Factory, ): VisaActivationRepository.Factory + + // @Binds + // fun bindVisaRepository(repository: DefaultVisaRepository): VisaRepository + + // Mocked + @Binds + fun bindVisaRepository(repository: MockVisaRepository): VisaRepository } \ No newline at end of file diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/CurrencyUtils.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/CurrencyUtils.kt similarity index 100% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/utils/CurrencyUtils.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/utils/CurrencyUtils.kt diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaApiRequestMaker.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaApiRequestMaker.kt new file mode 100644 index 0000000000..538c665358 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaApiRequestMaker.kt @@ -0,0 +1,112 @@ +package com.tangem.data.visa.utils + +import com.tangem.data.visa.converter.AccessCodeDataConverter +import com.tangem.data.visa.model.AccessCodeData +import com.tangem.datasource.api.common.response.ApiResponse +import com.tangem.datasource.api.common.response.ApiResponseError +import com.tangem.datasource.api.common.response.getOrThrow +import com.tangem.datasource.api.visa.TangemVisaAuthApi +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.visa.exception.RefreshTokenExpiredException +import com.tangem.domain.visa.model.VisaAuthTokens +import com.tangem.domain.visa.model.VisaCardActivationStatus +import com.tangem.domain.visa.model.getAuthHeader +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 javax.inject.Inject +import kotlin.jvm.Throws + +typealias VisaAuthorizationHeader = String + +internal class VisaApiRequestMaker @Inject constructor( + private val userWalletsStore: UserWalletsStore, + private val visaAuthApi: TangemVisaAuthApi, + private val accessCodeDataConverter: AccessCodeDataConverter, + private val dispatcherProvider: CoroutineDispatcherProvider, +) { + suspend fun request( + userWalletId: UserWalletId, + requestBlock: suspend (header: VisaAuthorizationHeader, accessCodeData: AccessCodeData) -> ApiResponse, + ): T = withContext(dispatcherProvider.io) { + val authTokens = getAuthTokens(userWalletId) + val authHeader = authTokens.getAuthHeader() + val accessCodeData = accessCodeDataConverter.convert(authTokens) + + runCatching { + requestBlock(authHeader, accessCodeData).getOrThrow() + }.getOrElse { responseError -> + if (responseError !is ApiResponseError.HttpException || + responseError.code != ApiResponseError.HttpException.Code.UNAUTHORIZED + ) { + throw responseError + } + + val newTokens = runCatching { + refreshAccessTokens(authTokens.refreshToken) + }.getOrElse { + if (it is ApiResponseError.HttpException && + it.code == ApiResponseError.HttpException.Code.UNAUTHORIZED + ) { + userWalletsStore.update(userWalletId) { userWallet -> + userWallet.copy( + scanResponse = userWallet.scanResponse.copy( + visaCardActivationStatus = VisaCardActivationStatus.RefreshTokenExpired, + ), + ) + } + } + throw RefreshTokenExpiredException() + } + + userWalletsStore.update(userWalletId) { userWallet -> + userWallet.copy( + scanResponse = userWallet.scanResponse.copy( + visaCardActivationStatus = VisaCardActivationStatus.Activated( + visaAuthTokens = newTokens, + ), + ), + ) + } + + val newAuthHeader = newTokens.getAuthHeader() + val newAccessCodeData = accessCodeDataConverter.convert(newTokens) + + requestBlock(newAuthHeader, newAccessCodeData).getOrThrow() + } + } + + private suspend fun refreshAccessTokens(refreshToken: VisaAuthTokens.RefreshToken): VisaAuthTokens { + val result = visaAuthApi.refreshAccessToken(refreshToken.value).getOrThrow() + + return VisaAuthTokens( + accessToken = result.accessToken, + refreshToken = VisaAuthTokens.RefreshToken(result.refreshToken), + ) + } + + @Throws + private suspend fun getAuthTokens(userWalletId: UserWalletId): VisaAuthTokens { + val userWallet = findVisaUserWallet(userWalletId) + val status = userWallet.scanResponse.visaCardActivationStatus ?: error("Visa card activation status not found") + + if (status is VisaCardActivationStatus.RefreshTokenExpired) { + throw RefreshTokenExpiredException() + } + + return (status as? VisaCardActivationStatus.Activated)?.visaAuthTokens ?: error("Visa card is not activated") + } + + private suspend fun findVisaUserWallet(userWalletId: UserWalletId): UserWallet { + val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { + "No user wallet found: $userWalletId" + } + if (!userWallet.scanResponse.cardTypesResolver.isVisaWallet()) { + error("VISA wallet required: $userWalletId") + } + + return userWallet + } +} \ No newline at end of file diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaConstants.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaConstants.kt similarity index 86% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaConstants.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaConstants.kt index 27cd336588..1a6f0a142a 100644 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaConstants.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaConstants.kt @@ -15,9 +15,9 @@ internal object VisaConstants { ) /* - * Must be `false` in production - * Don't forget to change CardTypesResolver.isVisaWallet - * */ + * Must be `false` in production + * Don't forget to change CardTypesResolver.isVisaWallet + * */ const val IS_DEMO_MODE_ENABLED = false const val USE_TEST_ENV = true @@ -40,5 +40,7 @@ internal fun getDemoAddress(): String { internal fun getDemoPublicKey(): String { return if (VisaConstants.USE_TEST_ENV) { VisaConstants.DEMO_TESTNET_PUBLIC_KEY - } else VisaConstants.DEMO_MAINNET_PUBLIC_KEY + } else { + VisaConstants.DEMO_MAINNET_PUBLIC_KEY + } } \ No newline at end of file diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt similarity index 97% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt index 6398dcec84..989a2d504c 100644 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt @@ -32,7 +32,6 @@ internal class VisaCurrencyFactory { available = balances.available.forPayment, blocked = balances.blocked, debt = balances.debt, - pendingRefund = balances.pendingRefund, ) }, limits = VisaCurrency.Limits( diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxDetailsFactory.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxDetailsFactory.kt similarity index 97% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxDetailsFactory.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxDetailsFactory.kt index 5108e0387e..c69adf5664 100644 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxDetailsFactory.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxDetailsFactory.kt @@ -2,8 +2,8 @@ package com.tangem.data.visa.utils import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.externallinkprovider.TxExploreState +import com.tangem.datasource.api.visa.models.response.VisaTxHistoryResponse import com.tangem.domain.visa.model.VisaTxDetails -import com.tangem.lib.visa.model.VisaTxHistoryResponse internal class VisaTxDetailsFactory { diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt similarity index 89% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt index 86d1439068..75a8c4eefe 100644 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt @@ -1,7 +1,7 @@ package com.tangem.data.visa.utils +import com.tangem.datasource.api.visa.models.response.VisaTxHistoryResponse import com.tangem.domain.visa.model.VisaTxHistoryItem -import com.tangem.lib.visa.model.VisaTxHistoryResponse internal class VisaTxHistoryItemFactory { diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt similarity index 92% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt index e233435e55..4393d4f372 100644 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt @@ -3,13 +3,9 @@ package com.tangem.data.visa.utils import androidx.paging.PagingSource import androidx.paging.PagingState import com.tangem.data.common.cache.CacheRegistry -import com.tangem.datasource.api.common.response.ApiResponseError -import com.tangem.datasource.api.common.response.getOrThrow -import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider +import com.tangem.datasource.api.visa.models.response.VisaTxHistoryResponse import com.tangem.domain.visa.model.VisaTxHistoryItem import com.tangem.domain.wallets.models.UserWallet -import com.tangem.lib.visa.api.VisaApi -import com.tangem.lib.visa.model.VisaTxHistoryResponse import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update 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 0d0c0f38ee..e17b2c880c 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 @@ -11,6 +11,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.PreferencesKeys.SEED_FIRST_NOTIFICATION_SHOW_TIME import com.tangem.datasource.local.preferences.utils.get +import com.tangem.datasource.local.preferences.utils.getObjectMap import com.tangem.datasource.local.preferences.utils.getSyncOrDefault import com.tangem.datasource.local.preferences.utils.store import com.tangem.datasource.local.userwallet.UserWalletsStore @@ -202,4 +203,28 @@ internal class DefaultWalletsRepository( } } } + + override fun nftEnabledStatus(userWalletId: UserWalletId): Flow = appPreferencesStore + .getObjectMap(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY) + .map { it[userWalletId.stringValue] == true } + + override suspend fun enableNFT(userWalletId: UserWalletId) { + appPreferencesStore.editData { + it.setObjectMap( + key = PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY, + value = it.getObjectMap(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY) + .plus(userWalletId.stringValue to true), + ) + } + } + + override suspend fun disableNFT(userWalletId: UserWalletId) { + appPreferencesStore.editData { + it.setObjectMap( + key = PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY, + value = it.getObjectMap(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY) + .plus(userWalletId.stringValue to false), + ) + } + } } \ No newline at end of file diff --git a/domain/analytics/build.gradle.kts b/domain/analytics/build.gradle.kts index f649160e1e..907caaa71d 100644 --- a/domain/analytics/build.gradle.kts +++ b/domain/analytics/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(deps.plugins.kotlin.jvm) alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.ksp) id("configuration") } @@ -11,5 +12,7 @@ dependencies { implementation(projects.domain.core) implementation(projects.domain.wallets.models) implementation(deps.moshi.kotlin) - kapt(deps.moshi.kotlin.codegen) + implementation(deps.arrow.core) + implementation(deps.arrow.fx) + ksp(deps.moshi.kotlin.codegen) } \ No newline at end of file diff --git a/domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt b/domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt index a7b9ab2d90..0c43c9717c 100644 --- a/domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt +++ b/domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt @@ -417,6 +417,11 @@ class DemoConfig { "AF15000001195781", "AF15000001195773", "AF15000001195799", + // Wallet 2 QA + "AF12345678912346", + "AF12345678912361", + "AF10100000000076", + "AF10100000000084", ) @Suppress("ClassOrdering") diff --git a/domain/legacy/build.gradle.kts b/domain/legacy/build.gradle.kts index 8a3a831cbe..11e080b895 100644 --- a/domain/legacy/build.gradle.kts +++ b/domain/legacy/build.gradle.kts @@ -1,9 +1,7 @@ -import com.tangem.plugin.configuration.configurations.extension.kaptForObfuscatingVariants - plugins { alias(deps.plugins.android.library) alias(deps.plugins.kotlin.android) - alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.ksp) id("configuration") } @@ -41,7 +39,7 @@ dependencies { implementation(deps.moshi.kotlin) implementation(deps.reKotlin) implementation(deps.timber) - kaptForObfuscatingVariants(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) /** Testing libraries */ testImplementation(deps.test.junit) diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/configs/Wallet2CardConfig.kt b/domain/legacy/src/main/java/com/tangem/domain/common/configs/Wallet2CardConfig.kt index 79355cd5c6..aa0a5e23e6 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/configs/Wallet2CardConfig.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/configs/Wallet2CardConfig.kt @@ -201,6 +201,10 @@ data object Wallet2CardConfig : CardConfig { Blockchain.KaspaTestnet -> EllipticCurve.Secp256k1 Blockchain.Alephium -> EllipticCurve.Secp256k1 Blockchain.AlephiumTestnet -> EllipticCurve.Secp256k1 + Blockchain.Scroll -> EllipticCurve.Secp256k1 + Blockchain.ScrollTestnet -> EllipticCurve.Secp256k1 + Blockchain.ZkLinkNova -> EllipticCurve.Secp256k1 + Blockchain.ZkLinkNovaTestnet -> EllipticCurve.Secp256k1 } } } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaUtilities.kt b/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaUtilities.kt index de113d6a18..fbd488d355 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaUtilities.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaUtilities.kt @@ -6,6 +6,7 @@ import com.tangem.common.card.FirmwareVersion import com.tangem.domain.models.scan.CardDTO private const val VISA_BATCH_START = "AE" +private const val VISA_BATCH_START_2 = "FFFC" object VisaUtilities { @@ -24,6 +25,6 @@ object VisaUtilities { fun isVisaCard(firmwareVersion: Double, batchId: String): Boolean { return firmwareVersion in FirmwareVersion.visaRange && - batchId.startsWith(VISA_BATCH_START) + (batchId.startsWith(VISA_BATCH_START) || batchId.startsWith(VISA_BATCH_START_2)) } } \ No newline at end of file diff --git a/domain/legacy/src/test/java/com/tangem/domain/common/configs/Wallet2CardConfigTest.kt b/domain/legacy/src/test/java/com/tangem/domain/common/configs/Wallet2CardConfigTest.kt index 3faea63878..d80b9be554 100644 --- a/domain/legacy/src/test/java/com/tangem/domain/common/configs/Wallet2CardConfigTest.kt +++ b/domain/legacy/src/test/java/com/tangem/domain/common/configs/Wallet2CardConfigTest.kt @@ -158,6 +158,10 @@ class Wallet2CardConfigTest { Blockchain.KaspaTestnet to EllipticCurve.Secp256k1, Blockchain.Alephium to EllipticCurve.Secp256k1, Blockchain.AlephiumTestnet to EllipticCurve.Secp256k1, + Blockchain.Scroll to EllipticCurve.Secp256k1, + Blockchain.ScrollTestnet to EllipticCurve.Secp256k1, + Blockchain.ZkLinkNova to EllipticCurve.Secp256k1, + Blockchain.ZkLinkNovaTestnet to EllipticCurve.Secp256k1, ) @Test diff --git a/domain/models/build.gradle.kts b/domain/models/build.gradle.kts index aeafc373d1..38288fbd08 100644 --- a/domain/models/build.gradle.kts +++ b/domain/models/build.gradle.kts @@ -3,7 +3,7 @@ import com.tangem.plugin.configuration.configurations.extension.kaptForObfuscati plugins { alias(deps.plugins.kotlin.jvm) alias(deps.plugins.kotlin.serialization) - alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.ksp) id("configuration") } @@ -14,5 +14,5 @@ dependencies { implementation(deps.moshi.kotlin) implementation(deps.moshi.adapters) implementation(deps.kotlin.serialization) - kapt(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) } \ No newline at end of file diff --git a/domain/onramp/models/build.gradle.kts b/domain/onramp/models/build.gradle.kts index b717a7ae41..7428002d0d 100644 --- a/domain/onramp/models/build.gradle.kts +++ b/domain/onramp/models/build.gradle.kts @@ -1,7 +1,7 @@ plugins { alias(deps.plugins.kotlin.jvm) alias(deps.plugins.kotlin.serialization) - alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.ksp) id("configuration") } @@ -11,7 +11,7 @@ dependencies { implementation(projects.domain.wallets.models) implementation(deps.moshi.kotlin) - kapt(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) implementation(deps.kotlin.serialization) implementation(deps.jodatime) } \ No newline at end of file diff --git a/domain/staking/models/build.gradle.kts b/domain/staking/models/build.gradle.kts index ca12daff07..10abc551b9 100644 --- a/domain/staking/models/build.gradle.kts +++ b/domain/staking/models/build.gradle.kts @@ -3,7 +3,7 @@ import com.tangem.plugin.configuration.configurations.extension.kaptForObfuscati plugins { alias(deps.plugins.kotlin.jvm) alias(deps.plugins.kotlin.serialization) - alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.ksp) id("configuration") } @@ -15,5 +15,5 @@ dependencies { implementation(deps.jodatime) implementation(deps.moshi) - kaptForObfuscatingVariants(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) } \ No newline at end of file diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionCommonType.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionCommonType.kt index 120dfd0c18..605393dfb4 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionCommonType.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionCommonType.kt @@ -2,7 +2,7 @@ package com.tangem.domain.staking.model.stakekit.action sealed class StakingActionCommonType { - data object Enter : StakingActionCommonType() + data class Enter(val skipEnterAmount: Boolean) : StakingActionCommonType() data class Exit(val partiallyUnstakeDisabled: Boolean) : StakingActionCommonType() sealed class Pending : StakingActionCommonType() { data object Restake : Pending() diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionType.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionType.kt index 01e02cea2f..467bb051aa 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionType.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionType.kt @@ -39,4 +39,12 @@ enum class StakingActionType { MIGRATE -> "Migrate" UNKNOWN -> "Unknown" } + + val isRestake + get() = when (this) { + RESTAKE, + STAKE, + -> true + else -> false + } } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/toggles/StakingFeatureToggles.kt b/domain/staking/src/main/java/com/tangem/domain/staking/toggles/StakingFeatureToggles.kt index 47949204f9..33ae4375b0 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/toggles/StakingFeatureToggles.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/toggles/StakingFeatureToggles.kt @@ -2,4 +2,5 @@ package com.tangem.domain.staking.toggles interface StakingFeatureToggles { val isTonStakingEnabled: Boolean + val isCardanoStakingEnabled: Boolean } \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenExchangeAnalyticsEvent.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenExchangeAnalyticsEvent.kt index 9df2d8a224..7fe8dfdf64 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenExchangeAnalyticsEvent.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenExchangeAnalyticsEvent.kt @@ -26,17 +26,34 @@ class TokenExchangeAnalyticsEvent( ) class GoToProviderStatus(token: String) : TokenScreenAnalyticsEvent( - event = "Button - Go To Provider", + event = BUTTON_GO_TO_PROVIDER, params = mapOf(TOKEN_PARAM to token, PLACE to "Status"), ) class GoToProviderKYC(token: String) : TokenScreenAnalyticsEvent( - event = "Button - Go To Provider", + event = BUTTON_GO_TO_PROVIDER, params = mapOf(TOKEN_PARAM to token, PLACE to "KYC"), ) class GoToProviderFail(token: String) : TokenScreenAnalyticsEvent( - event = "Button - Go To Provider", + event = BUTTON_GO_TO_PROVIDER, params = mapOf(TOKEN_PARAM to token, PLACE to "Fail"), ) + + class GoToProviderLongTime(token: String) : TokenScreenAnalyticsEvent( + event = BUTTON_GO_TO_PROVIDER, + params = mapOf(TOKEN_PARAM to token, PLACE to "LongTime"), + ) + + class LongTimeTransaction(token: String, provider: String) : TokenScreenAnalyticsEvent( + event = "Notice - Long Time Transaction", + params = mapOf( + TOKEN_PARAM to token, + PROVIDER to provider, + ), + ) + + private companion object { + const val BUTTON_GO_TO_PROVIDER = "Button - Go To Provider" + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt index 1fc7bfdf64..2a655fba4c 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt @@ -7,6 +7,7 @@ import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TotalFiatBalance import com.tangem.lib.crypto.BlockchainUtils +import com.tangem.lib.crypto.BlockchainUtils.isIncludeStakingTotalBalance import com.tangem.utils.extensions.orZero import java.math.BigDecimal @@ -21,6 +22,8 @@ internal class TokenListFiatBalanceOperations( if (isAnyTokenLoading) return fiatBalance for (token in currencies) { + val networkId = token.currency.network.id.value + val includeStakingBalance = isIncludeStakingTotalBalance(networkId) when (val status = token.value) { is CryptoCurrencyStatus.Loading -> { fiatBalance = TotalFiatBalance.Loading @@ -35,7 +38,7 @@ internal class TokenListFiatBalanceOperations( is CryptoCurrencyStatus.Unreachable, is CryptoCurrencyStatus.NoAmount, -> { - if (BlockchainUtils.isIncludeToBalanceOnError(token.currency.network.id.value)) { + if (BlockchainUtils.isIncludeToBalanceOnError(networkId)) { fiatBalance = recalculateNoAccountBalance(status, fiatBalance) } else { fiatBalance = TotalFiatBalance.Failed @@ -46,10 +49,10 @@ internal class TokenListFiatBalanceOperations( fiatBalance = recalculateNoAccountBalance(status, fiatBalance) } is CryptoCurrencyStatus.Loaded -> { - fiatBalance = recalculateBalance(status, fiatBalance) + fiatBalance = recalculateBalance(status, fiatBalance, includeStakingBalance) } is CryptoCurrencyStatus.Custom -> { - fiatBalance = recalculateBalance(status, fiatBalance) + fiatBalance = recalculateBalance(status, fiatBalance, includeStakingBalance) } } } @@ -74,12 +77,16 @@ internal class TokenListFiatBalanceOperations( private fun recalculateBalance( status: CryptoCurrencyStatus.Loaded, currentBalance: TotalFiatBalance, + includeStakingBalance: Boolean, ): TotalFiatBalance { return with(currentBalance) { val yieldBalance = status.yieldBalance as? YieldBalance.Data val stakingBalance = yieldBalance?.getTotalWithRewardsStakingBalance().orZero() - val fiatStakingBalance = status.fiatRate.times(stakingBalance) - + val fiatStakingBalance = if (includeStakingBalance) { + status.fiatRate.times(stakingBalance) + } else { + BigDecimal.ZERO + } (this as? TotalFiatBalance.Loaded)?.copy( amount = this.amount + status.fiatAmount + fiatStakingBalance, ) ?: TotalFiatBalance.Loaded( @@ -93,11 +100,16 @@ internal class TokenListFiatBalanceOperations( private fun recalculateBalance( status: CryptoCurrencyStatus.Custom, currentBalance: TotalFiatBalance, + includeStakingBalance: Boolean, ): TotalFiatBalance { return with(currentBalance) { val isTokenAmountCanBeSummarized = status.fiatAmount != null val yieldBalance = (status.yieldBalance as? YieldBalance.Data)?.getTotalWithRewardsStakingBalance().orZero() - val fiatYieldBalance = status.fiatRate?.times(yieldBalance).orZero() + val fiatYieldBalance = if (includeStakingBalance) { + status.fiatRate?.times(yieldBalance).orZero() + } else { + BigDecimal.ZERO + } (this as? TotalFiatBalance.Loaded)?.copy( amount = this.amount + status.fiatAmount.orZero() + fiatYieldBalance, isAllAmountsSummarized = isTokenAmountCanBeSummarized, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt index 6e27d5c2d7..be9623f89e 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt @@ -9,6 +9,7 @@ import arrow.core.raise.ensureNotNull import arrow.core.toNonEmptyListOrNull import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.tokens.model.* +import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.extensions.orZero import java.math.BigDecimal @@ -99,7 +100,11 @@ internal class TokenListSortingOperations( private fun CryptoCurrencyStatus.getTotalBalance(): BigDecimal { val yieldBalance = value.yieldBalance as? YieldBalance.Data val totalYieldBalance = yieldBalance?.getTotalWithRewardsStakingBalance().orZero() - val totalFiatYieldBalance = totalYieldBalance.multiply(value.fiatRate.orZero()) + val totalFiatYieldBalance = if (!BlockchainUtils.isIncludeStakingTotalBalance(currency.network.id.value)) { + totalYieldBalance.multiply(value.fiatRate.orZero()) + } else { + BigDecimal.ZERO + } return value.fiatAmount?.plus(totalFiatYieldBalance).orZero() } diff --git a/domain/transaction/models/src/main/kotlin/com/tangem/domain/transaction/error/GetFeeError.kt b/domain/transaction/models/src/main/kotlin/com/tangem/domain/transaction/error/GetFeeError.kt index 48fd54937f..ef18d2916a 100644 --- a/domain/transaction/models/src/main/kotlin/com/tangem/domain/transaction/error/GetFeeError.kt +++ b/domain/transaction/models/src/main/kotlin/com/tangem/domain/transaction/error/GetFeeError.kt @@ -7,5 +7,6 @@ sealed class GetFeeError { sealed class BlockchainErrors : GetFeeError() { data object TronActivationError : BlockchainErrors() data object KaspaZeroUtxo : BlockchainErrors() + data object SuiOneCoinRequired : BlockchainErrors() } } \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/ErrorsMapper.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/ErrorsMapper.kt index 5aa646c485..db5b877d46 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/ErrorsMapper.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/ErrorsMapper.kt @@ -16,6 +16,9 @@ fun Result.Failure.mapToFeeError(): GetFeeError { is BlockchainSdkError.Kaspa.ZeroUtxoError -> { GetFeeError.BlockchainErrors.KaspaZeroUtxo } + is BlockchainSdkError.Sui.OneSuiRequired -> { + GetFeeError.BlockchainErrors.SuiOneCoinRequired + } else -> GetFeeError.DataError(this.error) } } diff --git a/domain/txhistory/build.gradle.kts b/domain/txhistory/build.gradle.kts index 892e55e639..269f30a989 100644 --- a/domain/txhistory/build.gradle.kts +++ b/domain/txhistory/build.gradle.kts @@ -20,4 +20,6 @@ dependencies { /** Android - Other */ implementation(deps.androidx.paging.runtime) + + api(projects.core.pagination) } \ No newline at end of file diff --git a/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/Page.kt b/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/Page.kt index 9e18e15403..4633490788 100644 --- a/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/Page.kt +++ b/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/Page.kt @@ -1,7 +1,7 @@ package com.tangem.domain.txhistory.models sealed class Page { - object Initial : Page() + data object Initial : Page() data class Next(val value: String) : Page() - object LastPage : Page() + data object LastPage : Page() } \ No newline at end of file diff --git a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/model/TxHistoryListConfig.kt b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/model/TxHistoryListConfig.kt new file mode 100644 index 0000000000..303b464ff3 --- /dev/null +++ b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/model/TxHistoryListConfig.kt @@ -0,0 +1,6 @@ +package com.tangem.domain.txhistory.model + +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId + +data class TxHistoryListConfig(val userWalletId: UserWalletId, val currency: CryptoCurrency, val refresh: Boolean) \ No newline at end of file diff --git a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/model/TxHistoryTypealiases.kt b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/model/TxHistoryTypealiases.kt new file mode 100644 index 0000000000..334b31fc10 --- /dev/null +++ b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/model/TxHistoryTypealiases.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.txhistory.model + +import com.tangem.domain.txhistory.models.PaginationWrapper +import com.tangem.domain.txhistory.models.TxHistoryItem +import com.tangem.pagination.BatchFlow +import com.tangem.pagination.BatchingContext + +typealias TxHistoryListBatchingContext = BatchingContext + +typealias TxHistoryListBatchFlow = BatchFlow, Nothing> \ No newline at end of file diff --git a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/repository/TxHistoryRepositoryV2.kt b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/repository/TxHistoryRepositoryV2.kt new file mode 100644 index 0000000000..ae9ea875ea --- /dev/null +++ b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/repository/TxHistoryRepositoryV2.kt @@ -0,0 +1,9 @@ +package com.tangem.domain.txhistory.repository + +import com.tangem.domain.txhistory.model.TxHistoryListBatchFlow +import com.tangem.domain.txhistory.model.TxHistoryListBatchingContext + +interface TxHistoryRepositoryV2 { + + fun getTxHistoryBatchFlow(batchSize: Int, context: TxHistoryListBatchingContext): TxHistoryListBatchFlow +} \ No newline at end of file diff --git a/domain/visa/build.gradle.kts b/domain/visa/build.gradle.kts index 96fb032e84..8ce37bc6d1 100644 --- a/domain/visa/build.gradle.kts +++ b/domain/visa/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(deps.plugins.android.library) alias(deps.plugins.kotlin.android) + alias(deps.plugins.ksp) id("configuration") } @@ -24,4 +25,5 @@ dependencies { implementation(deps.androidx.paging.runtime) implementation(deps.moshi) implementation(deps.moshi.kotlin) + ksp(deps.moshi.kotlin.codegen) } \ No newline at end of file diff --git a/domain/visa/models/build.gradle.kts b/domain/visa/models/build.gradle.kts index 96a8bc4639..b2df1fba1f 100644 --- a/domain/visa/models/build.gradle.kts +++ b/domain/visa/models/build.gradle.kts @@ -1,11 +1,13 @@ plugins { alias(deps.plugins.kotlin.jvm) alias(deps.plugins.kotlin.serialization) + alias(deps.plugins.ksp) id("configuration") } dependencies { implementation(deps.moshi.kotlin) + ksp(deps.moshi.kotlin.codegen) implementation(deps.moshi.adapters) implementation(deps.kotlin.serialization) } \ No newline at end of file diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaActivationOrderInfo.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaActivationOrderInfo.kt index b8320e7678..78bbd9692b 100644 --- a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaActivationOrderInfo.kt +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaActivationOrderInfo.kt @@ -1,9 +1,11 @@ package com.tangem.domain.visa.model import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass import kotlinx.serialization.Serializable @Serializable +@JsonClass(generateAdapter = true) data class VisaActivationOrderInfo( @Json(name = "orderId") val orderId: String, @Json(name = "customer_id") val customerId: String, diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/SetVisaPinCodeUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/SetVisaPinCodeUseCase.kt index 7b05cf26b8..6fda7af7df 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/visa/SetVisaPinCodeUseCase.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/SetVisaPinCodeUseCase.kt @@ -18,8 +18,6 @@ import javax.crypto.spec.SecretKeySpec private const val KEY_SIZE = 256 -private const val RSA_PUB_KEY = "TODO" // TODO add RSA public key [REDACTED_TASK_KEY] - class SetVisaPinCodeUseCase( private val visaActivationRepositoryFactory: VisaActivationRepository.Factory, ) { @@ -30,9 +28,10 @@ class SetVisaPinCodeUseCase( pinCode: String, ): Either = Either.catch { val visaActivationRepository = visaActivationRepositoryFactory.create(visaCardId) + val rsaPublicKey = visaActivationRepository.getPinCodeRsaEncryptionPublicKey() val sessionKey = generateSessionKey() - val sessionId = getSessionId(RSA_PUB_KEY, sessionKey) + val sessionId = getSessionId(rsaPublicKey, sessionKey) val secureRandom = SecureRandom() val iv = ByteArray(size = 16) diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaCurrency.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaCurrency.kt index 1e6a0c55a1..b21df662c3 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaCurrency.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaCurrency.kt @@ -20,7 +20,6 @@ data class VisaCurrency( val available: BigDecimal, val blocked: BigDecimal, val debt: BigDecimal, - val pendingRefund: BigDecimal, ) data class Limits( diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt index 5f7959f423..f6d451fb7b 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt @@ -20,6 +20,8 @@ interface VisaActivationRepository { suspend fun sendPinCode(pinCode: VisaEncryptedPinCode) + suspend fun getPinCodeRsaEncryptionPublicKey(): String + interface Factory { fun create(cardId: VisaCardId): VisaActivationRepository } diff --git a/domain/wallets/models/build.gradle.kts b/domain/wallets/models/build.gradle.kts index 03afdb3da9..58d87775b5 100644 --- a/domain/wallets/models/build.gradle.kts +++ b/domain/wallets/models/build.gradle.kts @@ -1,7 +1,7 @@ plugins { alias(deps.plugins.kotlin.jvm) alias(deps.plugins.kotlin.serialization) - alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.ksp) id("configuration") } @@ -18,6 +18,6 @@ dependencies { implementation(deps.kotlin.serialization) implementation(deps.moshi.kotlin) implementation(deps.timber) - kapt(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) // endregion } \ 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 f226543d60..b1e526ce5c 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 @@ -29,4 +29,10 @@ interface WalletsRepository { suspend fun acceptSeedPhraseSecondNotification(userWalletId: UserWalletId) suspend fun markWallet2WasCreated(userWalletId: UserWalletId) + + fun nftEnabledStatus(userWalletId: UserWalletId): Flow + + suspend fun enableNFT(userWalletId: UserWalletId) + + suspend fun disableNFT(userWalletId: UserWalletId) } \ No newline at end of file diff --git a/features/ask-biometry/api/.gitignore b/features/ask-biometry/api/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/features/ask-biometry/api/.gitignore @@ -0,0 +1 @@ +/build diff --git a/features/ask-biometry/api/build.gradle.kts b/features/ask-biometry/api/build.gradle.kts new file mode 100644 index 0000000000..4e2e227056 --- /dev/null +++ b/features/ask-biometry/api/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + id("configuration") +} + +android { + namespace = "com.tangem.features.askbiometry" +} + +dependencies { + /* Project - Core */ + implementation(projects.core.decompose) + implementation(projects.core.ui) +} \ No newline at end of file diff --git a/features/ask-biometry/api/src/main/kotlin/com/tangem/features/askbiometry/AskBiometryComponent.kt b/features/ask-biometry/api/src/main/kotlin/com/tangem/features/askbiometry/AskBiometryComponent.kt new file mode 100644 index 0000000000..c01180af46 --- /dev/null +++ b/features/ask-biometry/api/src/main/kotlin/com/tangem/features/askbiometry/AskBiometryComponent.kt @@ -0,0 +1,20 @@ +package com.tangem.features.askbiometry + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.core.ui.decompose.ComposableContentComponent + +interface AskBiometryComponent : ComposableContentComponent, ComposableBottomSheetComponent { + + data class Params( + val bottomSheetVariant: Boolean, + val modelCallbacks: ModelCallbacks, + ) + + interface ModelCallbacks { + fun onAllowed() + fun onDenied() + } + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/ask-biometry/api/src/main/kotlin/com/tangem/features/askbiometry/AskBiometryFeatureToggles.kt b/features/ask-biometry/api/src/main/kotlin/com/tangem/features/askbiometry/AskBiometryFeatureToggles.kt new file mode 100644 index 0000000000..22425c768e --- /dev/null +++ b/features/ask-biometry/api/src/main/kotlin/com/tangem/features/askbiometry/AskBiometryFeatureToggles.kt @@ -0,0 +1,5 @@ +package com.tangem.features.askbiometry + +interface AskBiometryFeatureToggles { + val isEnabled: Boolean +} \ No newline at end of file diff --git a/features/ask-biometry/impl/.gitignore b/features/ask-biometry/impl/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/features/ask-biometry/impl/.gitignore @@ -0,0 +1 @@ +/build diff --git a/features/ask-biometry/impl/build.gradle.kts b/features/ask-biometry/impl/build.gradle.kts new file mode 100644 index 0000000000..c9d6bc3fac --- /dev/null +++ b/features/ask-biometry/impl/build.gradle.kts @@ -0,0 +1,52 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.kotlin.serialization) + alias(deps.plugins.hilt.android) + id("configuration") +} + +android { + namespace = "com.tangem.features.askbiometry.impl" +} + +dependencies { + api(projects.features.askBiometry.api) + + /** Core modules */ + implementation(projects.core.ui) + implementation(projects.core.res) + implementation(projects.core.decompose) + implementation(projects.core.analytics) + implementation(projects.core.analytics.models) + implementation(projects.core.configToggles) + implementation(projects.core.navigation) + + /** Domain */ + implementation(projects.domain.wallets) + implementation(projects.domain.wallets.models) + implementation(projects.domain.settings) + implementation(projects.domain.card) + + /** Compose libraries */ + implementation(deps.compose.material3) + implementation(deps.compose.animation) + implementation(deps.compose.foundation) + implementation(deps.compose.ui) + implementation(deps.compose.ui.tooling) + + /** Tangem libraries */ + implementation(projects.libs.tangemSdkApi) + implementation(tangemDeps.card.core) + implementation(tangemDeps.card.android) { + exclude(module = "joda-time") + } + + /** Other */ + implementation(deps.timber) + + /** DI */ + implementation(deps.hilt.android) + kapt(deps.hilt.kapt) +} \ No newline at end of file diff --git a/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/DefaultAskBiometryComponent.kt b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/DefaultAskBiometryComponent.kt new file mode 100644 index 0000000000..3f93ca138e --- /dev/null +++ b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/DefaultAskBiometryComponent.kt @@ -0,0 +1,63 @@ +package com.tangem.features.askbiometry.impl + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.features.askbiometry.AskBiometryComponent +import com.tangem.features.askbiometry.impl.model.AskBiometryModel +import com.tangem.features.askbiometry.impl.ui.AskBiometry +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultAskBiometryComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: AskBiometryComponent.Params, +) : AskBiometryComponent, AppComponentContext by appComponentContext { + + private val model: AskBiometryModel = getOrCreateModel(params) + + override fun dismiss() = model.dismiss() + + @Composable + override fun BottomSheet() { + val state by model.uiState.collectAsStateWithLifecycle() + val bsConfig = remember(this) { + TangemBottomSheetConfig( + isShown = true, + onDismissRequest = ::dismiss, + content = TangemBottomSheetConfigContent.Empty, + ) + } + + TangemBottomSheet( + config = bsConfig, + content = { AskBiometry(state) }, + ) + } + + @Composable + override fun Content(modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + + AskBiometry( + modifier = modifier, + state = state, + ) + } + + @AssistedFactory + interface Factory : AskBiometryComponent.Factory { + override fun create( + context: AppComponentContext, + params: AskBiometryComponent.Params, + ): DefaultAskBiometryComponent + } +} \ No newline at end of file diff --git a/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/DefaultAskBiometryFeatureToggles.kt b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/DefaultAskBiometryFeatureToggles.kt new file mode 100644 index 0000000000..e2be13dfb9 --- /dev/null +++ b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/DefaultAskBiometryFeatureToggles.kt @@ -0,0 +1,12 @@ +package com.tangem.features.askbiometry.impl + +import com.tangem.core.configtoggle.feature.FeatureTogglesManager +import com.tangem.features.askbiometry.AskBiometryFeatureToggles +import javax.inject.Inject + +class DefaultAskBiometryFeatureToggles @Inject constructor( + private val featureTogglesManager: FeatureTogglesManager, +) : AskBiometryFeatureToggles { + override val isEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled("ASK_BIOMETRY_REFACTORING_ENABLED") +} \ No newline at end of file diff --git a/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/di/FeatureModule.kt b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/di/FeatureModule.kt new file mode 100644 index 0000000000..e9e8bc3bc1 --- /dev/null +++ b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/di/FeatureModule.kt @@ -0,0 +1,36 @@ +package com.tangem.features.askbiometry.impl.di + +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.askbiometry.AskBiometryComponent +import com.tangem.features.askbiometry.AskBiometryFeatureToggles +import com.tangem.features.askbiometry.impl.DefaultAskBiometryComponent +import com.tangem.features.askbiometry.impl.DefaultAskBiometryFeatureToggles +import com.tangem.features.askbiometry.impl.model.AskBiometryModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface FeatureModule { + + @Binds + fun bindFeatureToggles(impl: DefaultAskBiometryFeatureToggles): AskBiometryFeatureToggles + + @Binds + fun bindComponentFactory(impl: DefaultAskBiometryComponent.Factory): AskBiometryComponent.Factory +} + +@Module +@InstallIn(ModelComponent::class) +internal interface ModelModule { + + @Binds + @IntoMap + @ClassKey(AskBiometryModel::class) + fun provideModel(model: AskBiometryModel): Model +} \ No newline at end of file diff --git a/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/model/AskBiometryModel.kt b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/model/AskBiometryModel.kt new file mode 100644 index 0000000000..a72832828f --- /dev/null +++ b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/model/AskBiometryModel.kt @@ -0,0 +1,130 @@ +package com.tangem.features.askbiometry.impl.model + +import androidx.compose.runtime.Stable +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.core.navigation.settings.SettingsManager +import com.tangem.core.ui.R +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.message.DialogMessage +import com.tangem.core.ui.message.EventMessageAction +import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.settings.SetSaveWalletScreenShownUseCase +import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.repository.WalletsRepository +import com.tangem.features.askbiometry.AskBiometryComponent +import com.tangem.features.askbiometry.impl.ui.state.AskBiometryUM +import com.tangem.sdk.api.TangemSdkManager +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +@Suppress("LongParameterList") +@Stable +@ModelScoped +internal class AskBiometryModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + paramsContainer: ParamsContainer, + private val setSaveWalletScreenShownUseCase: SetSaveWalletScreenShownUseCase, + private val settingsRepository: SettingsRepository, + private val tangemSdkManager: TangemSdkManager, + private val userWalletsListManager: UserWalletsListManager, + private val walletsRepository: WalletsRepository, + private val cardSdkConfigRepository: CardSdkConfigRepository, + private val settingsManager: SettingsManager, + private val uiMessageSender: UiMessageSender, +) : Model() { + + private val params = paramsContainer.require() + + private val _uiState = MutableStateFlow( + AskBiometryUM( + bottomSheetVariant = params.bottomSheetVariant, + onAllowClick = ::onAllowClick, + onDontAllowClick = ::dontAllow, + ), + ) + val uiState = _uiState.asStateFlow() + + init { + modelScope.launch { + setSaveWalletScreenShownUseCase() + } + } + + private fun onAllowClick() { + _uiState.update { it.copy(showProgress = true) } + allowToUseBiometrics() + } + + private fun dontAllow() { + params.modelCallbacks.onDenied() + } + + fun dismiss() { + dontAllow() + } + + private fun allowToUseBiometrics() { + modelScope.launch { + if (tangemSdkManager.checkNeedEnrollBiometrics()) { + showEnrollBiometricsDialog() + return@launch + } + + /* + + * because it will be automatically saved on UserWalletsListManager switch + */ + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync ?: run { + Timber.e("Unable to save user wallet") + uiMessageSender.send( + SnackbarMessage(stringReference("No selected user wallet")), + ) + + return@launch + } + + handleSuccessAllowing(selectedUserWallet) + } + } + + private suspend fun handleSuccessAllowing(userWallet: UserWallet) { + walletsRepository.saveShouldSaveUserWallets(item = true) + settingsRepository.setShouldSaveAccessCodes(value = true) + + cardSdkConfigRepository.setAccessCodeRequestPolicy( + isBiometricsRequestPolicy = userWallet.hasAccessCode, + ) + + params.modelCallbacks.onAllowed() + } + + private fun showEnrollBiometricsDialog() { + uiMessageSender.send( + DialogMessage( + message = resourceReference(R.string.save_user_wallet_agreement_enroll_biometrics_description), + title = resourceReference(R.string.save_user_wallet_agreement_enroll_biometrics_title), + firstAction = EventMessageAction( + title = resourceReference(R.string.common_enable), + onClick = { settingsManager.openBiometricSettings() }, + ), + secondAction = EventMessageAction( + title = resourceReference(R.string.common_cancel), + onClick = {}, + ), + dismissOnFirstAction = true, + ), + ) + } +} \ No newline at end of file diff --git a/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/ui/AskBiometry.kt b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/ui/AskBiometry.kt new file mode 100644 index 0000000000..09521a2b56 --- /dev/null +++ b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/ui/AskBiometry.kt @@ -0,0 +1,209 @@ +package com.tangem.features.askbiometry.impl.ui + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.* +import com.tangem.core.ui.components.atoms.Hand +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.askbiometry.impl.ui.state.AskBiometryUM +import com.tangem.features.askbiometry.impl.R + +@Composable +internal fun AskBiometry(state: AskBiometryUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Column( + modifier = Modifier.weight(1f), + ) { + if (state.bottomSheetVariant) { + Header(onCloseClick = state.onDontAllowClick) + } + + SpacerH32() + + Column( + modifier = Modifier.verticalScroll(rememberScrollState()), + ) { + Title(modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing22)) + SpacerH32() + Description( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing34) + .fillMaxWidth(), + ) + } + } + Footer(state = state) + SpacerH16() + } +} + +@Composable +private fun Header(onCloseClick: () -> Unit) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), + ) { + Hand() + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + IconButton( + modifier = Modifier.size(TangemTheme.dimens.size32), + onClick = onCloseClick, + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = painterResource(id = R.drawable.ic_close_24), + tint = TangemTheme.colors.icon.secondary, + contentDescription = stringResourceSafe(id = R.string.common_cancel), + ) + } + SpacerW8() + } + } +} + +@Composable +private fun Title(modifier: Modifier = Modifier) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing32), + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size56), + painter = painterResource(id = R.drawable.ic_fingerprint_24), + tint = TangemTheme.colors.icon.primary1, + contentDescription = null, + ) + Text( + text = stringResourceSafe(id = R.string.save_user_wallet_agreement_header_biometrics), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + } +} + +@Composable +private fun Description(modifier: Modifier = Modifier) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24), + horizontalAlignment = Alignment.Start, + ) { + DescriptionItem( + iconPainter = painterResource(id = R.drawable.ic_face_recognition_24), + title = stringResourceSafe(id = R.string.save_user_wallet_agreement_access_title), + description = stringResourceSafe(id = R.string.save_user_wallet_agreement_access_description), + ) + DescriptionItem( + iconPainter = painterResource(id = R.drawable.ic_lock_24), + title = stringResourceSafe(id = R.string.save_user_wallet_agreement_code_title), + description = stringResourceSafe(id = R.string.save_user_wallet_agreement_code_description_biometrics), + ) + } +} + +@Composable +private fun Footer(state: AskBiometryUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + showProgress = state.showProgress, + text = stringResourceSafe(id = R.string.save_user_wallet_agreement_allow_biometrics), + onClick = state.onAllowClick, + ) + + if (state.bottomSheetVariant.not()) { + SpacerH12() + + SecondaryButton( + modifier = Modifier.fillMaxWidth(), + text = stringResourceSafe(id = R.string.save_user_wallet_agreement_dont_allow), + onClick = state.onDontAllowClick, + ) + } + + SpacerH16() + + Text( + modifier = Modifier.fillMaxWidth(fraction = .7f), + text = stringResourceSafe(R.string.save_user_wallet_agreement_notice), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Center, + ) + } +} + +@Composable +private fun DescriptionItem(iconPainter: Painter, title: String, description: String) { + Row( + verticalAlignment = Alignment.Top, + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = iconPainter, + tint = TangemTheme.colors.icon.primary1, + contentDescription = null, + ) + SpacerW24() + Column { + Text( + text = title, + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + ) + SpacerH4() + Text( + text = description, + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun Preview() { + TangemThemePreview { + AskBiometry( + state = AskBiometryUM(), + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun PreviewBS() { + TangemThemePreview { + AskBiometry( + state = AskBiometryUM(bottomSheetVariant = true), + ) + } +} \ No newline at end of file diff --git a/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/ui/state/AskBiometryUM.kt b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/ui/state/AskBiometryUM.kt new file mode 100644 index 0000000000..27a511e379 --- /dev/null +++ b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/ui/state/AskBiometryUM.kt @@ -0,0 +1,11 @@ +package com.tangem.features.askbiometry.impl.ui.state + +import com.tangem.core.ui.extensions.TextReference + +internal data class AskBiometryUM( + val bottomSheetVariant: Boolean = false, + val showProgress: Boolean = false, + val error: TextReference? = null, + val onAllowClick: () -> Unit = {}, + val onDontAllowClick: () -> Unit = {}, +) \ No newline at end of file diff --git a/features/details/impl/build.gradle.kts b/features/details/impl/build.gradle.kts index 2ffdab4b0e..552f96b756 100644 --- a/features/details/impl/build.gradle.kts +++ b/features/details/impl/build.gradle.kts @@ -70,4 +70,6 @@ dependencies { implementation(deps.kotlin.immutable.collections) implementation(deps.reKotlin) implementation(deps.timber) + implementation(deps.arrow.core) + implementation(deps.arrow.fx) } \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/di/ModelModule.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/di/ModelModule.kt index 63481527c2..25599bd30e 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/di/ModelModule.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/di/ModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.details.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.details.model.DetailsModel import com.tangem.features.details.model.UserWalletListModel @@ -11,7 +11,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface ModelModule { @Binds diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt index e6694cee44..99def3ec10 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt @@ -2,7 +2,7 @@ package com.tangem.features.details.model import arrow.core.getOrElse import com.tangem.core.analytics.AppInstanceIdProvider -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router @@ -32,7 +32,7 @@ import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject -@ComponentScoped +@ModelScoped @Suppress("LongParameterList") internal class DetailsModel @Inject constructor( socialsBuilder: SocialsBuilder, diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt index d7cc987538..4b10237030 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt @@ -1,7 +1,7 @@ package com.tangem.features.details.model import com.tangem.common.ui.userwallet.state.UserWalletItemUM -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference @@ -19,7 +19,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.update import javax.inject.Inject -@ComponentScoped +@ModelScoped internal class UserWalletListModel @Inject constructor( userWalletsFetcher: UserWalletsFetcher, shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase, diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt index 70d3936e99..7cd8933ad5 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt @@ -1,7 +1,7 @@ package com.tangem.features.details.utils import com.tangem.common.routing.AppRoute -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.components.block.model.BlockUM import com.tangem.core.ui.extensions.resourceReference @@ -14,7 +14,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import javax.inject.Inject -@ComponentScoped +@ModelScoped internal class ItemsBuilder @Inject constructor(private val router: Router) { fun buildAll( diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/SocialsBuilder.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/SocialsBuilder.kt index 8282bbd41c..dda4068ab9 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/SocialsBuilder.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/SocialsBuilder.kt @@ -1,7 +1,7 @@ package com.tangem.features.details.utils import androidx.compose.ui.text.intl.Locale -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.navigation.url.UrlOpener import com.tangem.features.details.entity.DetailsFooterUM import com.tangem.features.details.impl.R @@ -9,7 +9,7 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import javax.inject.Inject -@ComponentScoped +@ModelScoped internal class SocialsBuilder @Inject constructor( private val urlOpener: UrlOpener, ) { diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt index fd621dbe14..6badebac2d 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt @@ -7,7 +7,7 @@ import arrow.core.raise.fold import arrow.core.raise.recover import com.tangem.common.routing.AppRoute import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.navigation.popTo import com.tangem.core.decompose.ui.UiMessageSender @@ -33,7 +33,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine import javax.inject.Inject import kotlin.coroutines.resume -@ComponentScoped +@ModelScoped @Suppress("LongParameterList") internal class UserWalletSaver @Inject constructor( private val scanCardProcessor: ScanCardProcessor, diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletsFetcher.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletsFetcher.kt index 4fce54d271..eef9deef0d 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletsFetcher.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletsFetcher.kt @@ -4,7 +4,7 @@ import arrow.core.Either import com.tangem.common.routing.AppRoute import com.tangem.common.ui.userwallet.converter.UserWalletItemUMConverter import com.tangem.common.ui.userwallet.state.UserWalletItemUM -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.extensions.resourceReference @@ -31,7 +31,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import javax.inject.Inject -@ComponentScoped +@ModelScoped internal class UserWalletsFetcher @Inject constructor( getWalletsUseCase: GetWalletsUseCase, private val getWalletTotalBalanceUseCase: GetWalletTotalBalanceUseCase, diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/di/ModelModule.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/di/ModelModule.kt index a589336c78..4862b935c9 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/di/ModelModule.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/di/ModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.disclaimer.impl.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.disclaimer.impl.model.DisclaimerModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface ModelModule { @Binds diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt index e2dffb37f3..f96cb66625 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt @@ -1,7 +1,7 @@ package com.tangem.features.disclaimer.impl.model import com.tangem.common.routing.AppRoute -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router @@ -17,7 +17,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import javax.inject.Inject -@ComponentScoped +@ModelScoped @Suppress("LongParameterList") internal class DisclaimerModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultAddCustomTokenComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultAddCustomTokenComponent.kt index 10cf6ddcc7..edceddb46d 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultAddCustomTokenComponent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultAddCustomTokenComponent.kt @@ -4,9 +4,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children -import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.extensions.compose.stack.Children +import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation +import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.stack.* import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.context.AppComponentContext diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultCustomTokenSelectorComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultCustomTokenSelectorComponent.kt index 2d690a862c..edd8f52aa9 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultCustomTokenSelectorComponent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultCustomTokenSelectorComponent.kt @@ -5,7 +5,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.slot.childSlot import com.arkivanov.decompose.router.slot.dismiss import com.tangem.core.decompose.context.AppComponentContext diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultManageTokensComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultManageTokensComponent.kt index 9a8b81b83a..a66edf956d 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultManageTokensComponent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultManageTokensComponent.kt @@ -6,7 +6,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.slot.childSlot import com.arkivanov.decompose.router.slot.dismiss import com.tangem.core.decompose.context.AppComponentContext diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/di/ModelModule.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/di/ModelModule.kt index 101e51cfdd..d9f4b0cb1d 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/di/ModelModule.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/di/ModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.managetokens.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.managetokens.model.CustomTokenFormModel import com.tangem.features.managetokens.model.CustomTokenSelectorModel @@ -13,7 +13,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface ModelModule { @Binds diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt index ad5db50c72..58b65ada79 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt @@ -2,7 +2,7 @@ package com.tangem.features.managetokens.model import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.ui.UiMessageSender @@ -37,7 +37,7 @@ import javax.inject.Inject // TODO: Divide to sub-components: [REDACTED_JIRA] @Suppress("LongParameterList", "LargeClass") -@ComponentScoped +@ModelScoped internal class CustomTokenFormModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val customCurrencyValidator: CustomCurrencyValidator, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenSelectorModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenSelectorModel.kt index 998307191a..b1b01ab5ed 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenSelectorModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenSelectorModel.kt @@ -3,7 +3,7 @@ package com.tangem.features.managetokens.model import arrow.core.getOrElse import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.ui.UiMessageSender @@ -33,7 +33,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject -@ComponentScoped +@ModelScoped internal class CustomTokenSelectorModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val getSupportedNetworksUseCase: GetSupportedNetworksUseCase, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt index 36916e335d..65be95e384 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt @@ -5,7 +5,7 @@ import arrow.core.getOrElse import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router @@ -42,7 +42,7 @@ import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList") -@ComponentScoped +@ModelScoped internal class ManageTokensModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val router: Router, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt index c5bb946e7d..485566a94a 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt @@ -3,7 +3,7 @@ package com.tangem.features.managetokens.model import arrow.core.flatten import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.ui.UiMessageSender @@ -37,7 +37,7 @@ import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList") -@ComponentScoped +@ModelScoped internal class OnboardingManageTokensModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val manageTokensListManager: ManageTokensListManager, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyFormBuilder.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyFormBuilder.kt index caf9507cc2..f371da280d 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyFormBuilder.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyFormBuilder.kt @@ -3,7 +3,7 @@ package com.tangem.features.managetokens.utils import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference @@ -15,7 +15,7 @@ import com.tangem.features.managetokens.impl.R import kotlinx.collections.immutable.persistentMapOf import javax.inject.Inject -@ComponentScoped +@ModelScoped internal class CustomCurrencyFormBuilder @Inject constructor( paramsContainer: ParamsContainer, ) { diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyValidator.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyValidator.kt index 77a41933ea..fa0f6b6046 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyValidator.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyValidator.kt @@ -1,7 +1,7 @@ package com.tangem.features.managetokens.utils import arrow.core.getOrElse -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.managetokens.CheckIsCurrencyNotAddedUseCase import com.tangem.domain.managetokens.CreateCurrencyUseCase import com.tangem.domain.managetokens.FindTokenUseCase @@ -20,7 +20,7 @@ import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject -@ComponentScoped +@ModelScoped internal class CustomCurrencyValidator @Inject constructor( private val validateTokenFormUseCase: ValidateTokenFormUseCase, private val createCustomCurrencyUseCase: CreateCurrencyUseCase, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt index d3a52095a7..7dde531506 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt @@ -4,7 +4,7 @@ import androidx.compose.ui.util.fastForEachIndexed import androidx.compose.ui.util.fastMap import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.core.ui.extensions.resourceReference @@ -40,7 +40,7 @@ import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList") -@ComponentScoped +@ModelScoped internal class ManageTokensListManager @Inject constructor( private val getManagedTokensUseCase: GetManagedTokensUseCase, private val checkHasLinkedTokensUseCase: CheckHasLinkedTokensUseCase, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/di/ModelModule.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/di/ModelModule.kt index 83e26ff055..2fda5dee58 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/di/ModelModule.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/di/ModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.markets.details.impl.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.markets.details.impl.model.MarketsTokenDetailsModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface ModelModule { @Binds diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt index 04d2edcb0c..3ba4dd807e 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt @@ -8,7 +8,7 @@ import com.tangem.common.ui.charts.state.MarketChartData import com.tangem.common.ui.charts.state.MarketChartDataProducer import com.tangem.common.ui.charts.state.sorted import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.url.UrlOpener @@ -53,7 +53,7 @@ import javax.inject.Inject @Suppress("LargeClass", "LongParameterList") @Stable -@ComponentScoped +@ModelScoped internal class MarketsTokenDetailsModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/DefaultMarketsEntryComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/DefaultMarketsEntryComponent.kt index ae0a8b3e09..d175090f39 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/DefaultMarketsEntryComponent.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/DefaultMarketsEntryComponent.kt @@ -6,7 +6,7 @@ import androidx.compose.runtime.State import androidx.compose.ui.Modifier import androidx.compose.ui.unit.Dp import com.arkivanov.decompose.ExperimentalDecomposeApi -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.stack.ChildStack import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.childStack diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/ui/EntryBottomSheetContent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/ui/EntryBottomSheetContent.kt index 5f30e89585..4adeb8b8c5 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/ui/EntryBottomSheetContent.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/ui/EntryBottomSheetContent.kt @@ -11,9 +11,9 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp -import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children -import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.slide -import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation +import com.arkivanov.decompose.extensions.compose.stack.Children +import com.arkivanov.decompose.extensions.compose.stack.animation.slide +import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation import com.arkivanov.decompose.router.stack.ChildStack import com.tangem.core.ui.res.LocalMainBottomSheetColor import com.tangem.core.ui.res.TangemTheme diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/di/ModelModule.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/di/ModelModule.kt index 38ea8f8688..2b35fe3c10 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/di/ModelModule.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/di/ModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.markets.portfolio.impl.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.markets.portfolio.impl.model.MarketsPortfolioModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface ModelModule { @Binds diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt index b28d495ac2..e31e23a7be 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt @@ -3,7 +3,7 @@ package com.tangem.features.markets.portfolio.impl.model import androidx.compose.runtime.Stable import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.ui.UiMessageSender @@ -38,7 +38,7 @@ import javax.inject.Inject @Suppress("LongParameterList") @Stable -@ComponentScoped +@ModelScoped internal class MarketsPortfolioModel @Inject constructor( paramsContainer: ParamsContainer, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt index 7b854e3c07..663a22a12f 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt @@ -2,7 +2,6 @@ package com.tangem.features.markets.portfolio.impl.model import com.tangem.common.routing.AppRoute import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.clipboard.ClipboardManager @@ -31,7 +30,6 @@ import dagger.assisted.AssistedInject import kotlinx.collections.immutable.toImmutableList @Suppress("LongParameterList") -@ComponentScoped internal class TokenActionsHandler @AssistedInject constructor( private val router: Router, private val clipboardManager: ClipboardManager, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/di/ModelModule.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/di/ModelModule.kt index fdb1ba90ba..e50f1793ed 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/di/ModelModule.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/di/ModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.markets.token.block.impl.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.markets.token.block.impl.model.TokenMarketBlockModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface ModelModule { @Binds diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/model/TokenMarketBlockModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/model/TokenMarketBlockModel.kt index 35346aeb94..372309d5ea 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/model/TokenMarketBlockModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/model/TokenMarketBlockModel.kt @@ -6,7 +6,7 @@ import com.tangem.common.routing.AppRoute import com.tangem.common.ui.charts.state.MarketChartData import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter import com.tangem.common.ui.charts.state.sorted -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router @@ -31,7 +31,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @Stable -@ComponentScoped +@ModelScoped internal class TokenMarketBlockModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/di/ModelModule.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/di/ModelModule.kt index 17aa3f4cd5..fca279c2f6 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/di/ModelModule.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/di/ModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.markets.tokenlist.impl.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.markets.tokenlist.impl.model.MarketsListModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface ModelModule { @Binds diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt index 7e94d860a3..65622d2f0d 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt @@ -3,7 +3,7 @@ package com.tangem.features.markets.tokenlist.impl.model import androidx.compose.runtime.Stable import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency @@ -29,7 +29,7 @@ private const val UPDATE_QUOTES_TIMER_MILLIS = 60000L private const val SEARCH_QUERY_DEBOUNCE_MILLIS = 800L @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) -@ComponentScoped +@ModelScoped @Stable internal class MarketsListModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, diff --git a/features/referral/presentation/.gitignore b/features/nft/api/.gitignore similarity index 100% rename from features/referral/presentation/.gitignore rename to features/nft/api/.gitignore diff --git a/features/nft/api/build.gradle.kts b/features/nft/api/build.gradle.kts new file mode 100644 index 0000000000..f94a0582c0 --- /dev/null +++ b/features/nft/api/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + id("configuration") +} + +android { + namespace = "com.tangem.features.nft.api" +} + +dependencies { + + /* Project - Domain */ + implementation(projects.domain.models) + + /* Project - Core */ + implementation(projects.core.decompose) + implementation(projects.core.ui) + + /* Compose */ + implementation(deps.compose.runtime) +} \ No newline at end of file diff --git a/features/nft/api/src/main/kotlin/com/tangem/features/nft/NFTFeatureToggles.kt b/features/nft/api/src/main/kotlin/com/tangem/features/nft/NFTFeatureToggles.kt new file mode 100644 index 0000000000..a3b0ad1cb5 --- /dev/null +++ b/features/nft/api/src/main/kotlin/com/tangem/features/nft/NFTFeatureToggles.kt @@ -0,0 +1,5 @@ +package com.tangem.features.nft + +interface NFTFeatureToggles { + val isNFTEnabled: Boolean +} \ No newline at end of file diff --git a/features/nft/impl/.gitignore b/features/nft/impl/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/nft/impl/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/nft/impl/build.gradle.kts b/features/nft/impl/build.gradle.kts new file mode 100644 index 0000000000..40d3b149c9 --- /dev/null +++ b/features/nft/impl/build.gradle.kts @@ -0,0 +1,66 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.kotlin.serialization) + alias(deps.plugins.hilt.android) + id("configuration") +} + +android { + namespace = "com.tangem.features.nft.impl" +} + +dependencies { + /** Api */ + implementation(projects.features.nft.api) + + /** Core modules */ + implementation(projects.core.configToggles) + implementation(projects.core.analytics) + implementation(projects.core.analytics.models) + implementation(projects.core.utils) + implementation(projects.core.ui) + implementation(projects.core.res) + implementation(projects.core.decompose) + implementation(projects.core.navigation) + implementation(projects.core.datasource) + + /** Common */ + implementation(projects.common.ui) + implementation(projects.common.routing) + + /** Tangem libraries */ + implementation(projects.libs.tangemSdkApi) + implementation(tangemDeps.card.core) + implementation(tangemDeps.card.android) { + exclude(module = "joda-time") + } + + /** AndroidX libraries */ + implementation(deps.androidx.core.ktx) + implementation(deps.lifecycle.runtime.ktx) + + /** Compose libraries */ + implementation(deps.compose.material) // to use buttons and text field in MultiWalletSeedPhraseImport.kt + implementation(deps.compose.material3) + implementation(deps.compose.animation) + implementation(deps.compose.foundation) + implementation(deps.compose.ui) + implementation(deps.compose.ui.tooling) + implementation(deps.compose.coil) + implementation(deps.lottie.compose) + implementation(deps.decompose.ext.compose) + implementation(deps.androidx.activity.compose) + implementation(deps.androidx.datastore) + + /** Other libraries */ + implementation(deps.kotlin.immutable.collections) + implementation(deps.kotlin.serialization) + implementation(deps.timber) + implementation(deps.firebase.crashlytics) + + /** DI */ + implementation(deps.hilt.android) + kapt(deps.hilt.kapt) +} \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/DefaultNFTFeatureToggles.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/DefaultNFTFeatureToggles.kt new file mode 100644 index 0000000000..7697217e52 --- /dev/null +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/DefaultNFTFeatureToggles.kt @@ -0,0 +1,10 @@ +package com.tangem.features.nft + +import com.tangem.core.configtoggle.feature.FeatureTogglesManager + +internal class DefaultNFTFeatureToggles( + private val featureTogglesManager: FeatureTogglesManager, +) : NFTFeatureToggles { + override val isNFTEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled(name = "NFT_ENABLED") +} \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/NFTFeatureModule.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/NFTFeatureModule.kt new file mode 100644 index 0000000000..b456b29b0b --- /dev/null +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/NFTFeatureModule.kt @@ -0,0 +1,19 @@ +package com.tangem.features.nft + +import com.tangem.core.configtoggle.feature.FeatureTogglesManager +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object NFTFeatureModule { + + @Provides + @Singleton + fun provideFeatureToggles(featureTogglesManager: FeatureTogglesManager): NFTFeatureToggles { + return DefaultNFTFeatureToggles(featureTogglesManager) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/OnboardingV2FeatureToggles.kt b/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/OnboardingV2FeatureToggles.kt index 17841eaf7b..1845005df6 100644 --- a/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/OnboardingV2FeatureToggles.kt +++ b/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/OnboardingV2FeatureToggles.kt @@ -3,4 +3,5 @@ package com.tangem.features.onboarding.v2 interface OnboardingV2FeatureToggles { val isOnboardingV2Enabled: Boolean val isVisaOnboardingEnabled: Boolean + val isNoteRefactoringEnabled: Boolean } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/DefaultOnboardingV2FeatureToggles.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/DefaultOnboardingV2FeatureToggles.kt index 956f744182..41b7796f7a 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/DefaultOnboardingV2FeatureToggles.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/DefaultOnboardingV2FeatureToggles.kt @@ -10,4 +10,6 @@ internal class DefaultOnboardingV2FeatureToggles @Inject constructor( get() = featureTogglesManager.isFeatureEnabled("ONBOARDING_CODE_REFACTORING_ENABLED") override val isVisaOnboardingEnabled: Boolean get() = featureTogglesManager.isFeatureEnabled("VISA_ONBOARDING_ENABLED") + override val isNoteRefactoringEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled("NOTE_REFACTORING_ENABLED") } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt index 6134fd218f..3c69bb169d 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt @@ -4,9 +4,11 @@ import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.stack.* +import com.arkivanov.decompose.value.ObserveLifecycleMode import com.arkivanov.decompose.value.Value +import com.arkivanov.decompose.value.subscribe import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child import com.tangem.core.decompose.context.childByContext @@ -87,7 +89,10 @@ internal class DefaultOnboardingEntryComponent @AssistedInject constructor( @Suppress("MagicNumber") private fun linkToInnerNavigation() { // stepper linking - innerStack.observe { stack -> + innerStack.subscribe( + lifecycle = lifecycle, + mode = ObserveLifecycleMode.CREATE_DESTROY, + ) { stack -> val activeChild = stack.active.instance if (activeChild is InnerNavigationHolder) { componentScope.launch { diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/di/ComponentModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/di/ComponentModule.kt index 1ea00b3585..4ce0d4c71d 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/di/ComponentModule.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/di/ComponentModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onboarding.v2.entry.impl.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent import com.tangem.features.onboarding.v2.entry.impl.DefaultOnboardingEntryComponent @@ -25,7 +25,7 @@ internal interface ComponentModule { } @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface ModelModule { @Binds @IntoMap diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt index 281ec29e57..ff272adfb8 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt @@ -3,12 +3,13 @@ package com.tangem.features.onboarding.v2.entry.impl.model import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.navigate import com.tangem.common.routing.AppRoute -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.settings.repositories.SettingsRepository @@ -25,7 +26,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import javax.inject.Inject -@ComponentScoped +@ModelScoped internal class OnboardingEntryModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, @@ -74,13 +75,24 @@ internal class OnboardingEntryModel @Inject constructor( titleProvider = titleProvider, onDone = ::onVisaOnboardingDone, ) + ProductType.Note -> OnboardingRoute.Note( + scanResponse = scanResponse, + titleProvider = titleProvider, + onDone = ::navigateToWalletScreen, + ) else -> error("Unsupported") } } private fun onMultiWalletOnboardingDone(userWallet: UserWallet) { - stackNavigation.navigate { - listOf(OnboardingRoute.ManageTokens(userWallet)) + if (userWallet.scanResponse.cardTypesResolver.isMultiwalletAllowed()) { + stackNavigation.navigate { + listOf(OnboardingRoute.ManageTokens(userWallet)) + } + } else { + stackNavigation.navigate { + listOf(OnboardingRoute.Done(onDone = ::navigateToWalletScreen)) + } } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt index 6e614deefd..21976c01cb 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt @@ -4,6 +4,7 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.features.managetokens.component.OnboardingManageTokensComponent import com.tangem.features.onboarding.v2.done.api.OnboardingDoneComponent import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent +import com.tangem.features.onboarding.v2.note.api.OnboardingNoteComponent import com.tangem.features.onboarding.v2.visa.api.OnboardingVisaComponent import javax.inject.Inject @@ -12,6 +13,7 @@ internal class OnboardingChildFactory @Inject constructor( private val onboardingManageTokensComponentFactory: OnboardingManageTokensComponent.Factory, private val onboardingDoneComponentFactory: OnboardingDoneComponent.Factory, private val onBoardingVisaComponentFactory: OnboardingVisaComponent.Factory, + private val onboardingNoteComponentFactory: OnboardingNoteComponent.Factory, ) { fun createChild(route: OnboardingRoute, childContext: AppComponentContext, onManageTokensDone: () -> Unit): Any { @@ -47,6 +49,14 @@ internal class OnboardingChildFactory @Inject constructor( onDone = route.onDone, ), ) + is OnboardingRoute.Note -> onboardingNoteComponentFactory.create( + context = childContext, + params = OnboardingNoteComponent.Params( + scanResponse = route.scanResponse, + titleProvider = route.titleProvider, + onDone = route.onDone, + ), + ) else -> Unit } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt index 69778da3b4..4686de8c0e 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt @@ -10,6 +10,12 @@ sealed class OnboardingRoute : Route { data object None : OnboardingRoute() + data class Note( + val titleProvider: TitleProvider, + val scanResponse: ScanResponse, + val onDone: () -> Unit, + ) : OnboardingRoute() + data class MultiWallet( val titleProvider: TitleProvider, val scanResponse: ScanResponse, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt index e9d2be883a..bfc7be83af 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt @@ -4,9 +4,9 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children -import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.slide -import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation +import com.arkivanov.decompose.extensions.compose.stack.Children +import com.arkivanov.decompose.extensions.compose.stack.animation.slide +import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation import com.arkivanov.decompose.router.stack.ChildStack import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.res.TangemTheme @@ -52,7 +52,12 @@ internal inline fun OnboardingEntry( modifier = modifier, ) } - OnboardingRoute.None -> {} + is OnboardingRoute.Note -> { + (it.instance as ComposableContentComponent).Content( + modifier = modifier, + ) + } + is OnboardingRoute.None -> {} } } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt index 51bf2171ca..0c783b964c 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt @@ -6,11 +6,11 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.decompose.FaultyDecomposeApi -import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children -import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.fade -import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.slide -import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.extensions.compose.stack.Children +import com.arkivanov.decompose.extensions.compose.stack.animation.fade +import com.arkivanov.decompose.extensions.compose.stack.animation.slide +import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation +import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.childSlot import com.arkivanov.decompose.router.slot.dismiss @@ -81,6 +81,8 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor backups = model.backups, ) + val backButtonClickFlow = MutableSharedFlow() + private val childStack: Value> = childStack( key = "innerStack", @@ -96,8 +98,6 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor }, ) - val backButtonClickFlow = MutableSharedFlow() - override val innerNavigation: InnerNavigation = object : InnerNavigation { override val state = innerNavigationStateFlow @@ -117,7 +117,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor source = bottomSheetNavigation, serializer = null, handleBackButton = false, - childFactory = { configuration, componentContext -> + childFactory = { _, componentContext -> MultiWalletAccessCodeComponent( context = childByContext(componentContext), params = childParams, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/model/MultiWalletAccessCodeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/model/MultiWalletAccessCodeModel.kt index a2f3f24de7..bd4a37a0b5 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/model/MultiWalletAccessCodeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/model/MultiWalletAccessCodeModel.kt @@ -3,7 +3,7 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.mode import androidx.compose.runtime.Stable import androidx.compose.ui.text.input.TextFieldValue import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.features.onboarding.v2.multiwallet.impl.analytics.OnboardingEvent @@ -22,7 +22,7 @@ import javax.inject.Inject private const val MINIMUM_ACCESS_CODE_LENGTH = 4 @Stable -@ComponentScoped +@ModelScoped internal class MultiWalletAccessCodeModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt index 48d3ead42a..46f6135d72 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt @@ -7,7 +7,7 @@ import com.tangem.common.core.TangemSdkError import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.ui.UiMessageSender @@ -38,7 +38,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @Stable -@ComponentScoped +@ModelScoped @Suppress("LongParameterList") class MultiWalletBackupModel @Inject constructor( paramsContainer: ParamsContainer, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/model/Wallet1ChooseOptionModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/model/Wallet1ChooseOptionModel.kt index 69d37f496f..c460852063 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/model/Wallet1ChooseOptionModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/model/Wallet1ChooseOptionModel.kt @@ -2,7 +2,7 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.mo import androidx.compose.runtime.Stable import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.card.repository.CardRepository @@ -21,7 +21,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @Stable -@ComponentScoped +@ModelScoped internal class Wallet1ChooseOptionModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, paramsContainer: ParamsContainer, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/ui/CarouselItems.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/ui/CarouselItems.kt new file mode 100644 index 0000000000..0af1cba768 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/ui/CarouselItems.kt @@ -0,0 +1,29 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.ui + +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.onboarding.v2.impl.R + +internal val CarouselItems = listOf( + CarouselItem( + title = resourceReference(R.string.onboarding_wallet_info_title_first), + subtitle = resourceReference(R.string.onboarding_wallet_info_subtitle_first), + ), + CarouselItem( + title = resourceReference(R.string.onboarding_wallet_info_title_second), + subtitle = resourceReference(R.string.onboarding_wallet_info_subtitle_second), + ), + CarouselItem( + title = resourceReference(R.string.onboarding_wallet_info_title_third), + subtitle = resourceReference(R.string.onboarding_wallet_info_subtitle_third), + ), + CarouselItem( + title = resourceReference(R.string.onboarding_wallet_info_title_fourth), + subtitle = resourceReference(R.string.onboarding_wallet_info_subtitle_fourth), + ), +) + +internal data class CarouselItem( + val title: TextReference, + val subtitle: TextReference, +) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/ui/Wallet1ChooseOption.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/ui/Wallet1ChooseOption.kt index b873a9e6b8..02d9a6eb9f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/ui/Wallet1ChooseOption.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/ui/Wallet1ChooseOption.kt @@ -1,10 +1,15 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.ui +import androidx.compose.animation.animateColorAsState +import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.* +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign @@ -12,6 +17,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -30,30 +36,44 @@ fun Wallet1ChooseOption( .navigationBarsPadding(), verticalArrangement = Arrangement.Bottom, ) { - // val pagerState = rememberPagerState(pageCount = { 10 }) // TODO [REDACTED_TASK_KEY] - Column( Modifier .fillMaxWidth() .verticalScroll(rememberScrollState()) - .padding(start = 32.dp, end = 32.dp, bottom = 16.dp) - .weight(1f), + .weight(1f) + .padding(top = 32.dp, bottom = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { - Text( - text = stringResourceSafe(R.string.onboarding_wallet_info_title_first), - style = TangemTheme.typography.h2, - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.Center, - modifier = Modifier.padding(top = 16.dp), - ) + val pagerState = rememberPagerState { CarouselItems.size } - Text( - text = stringResourceSafe(R.string.onboarding_wallet_info_subtitle_first), - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.secondary, - textAlign = TextAlign.Center, - modifier = Modifier.padding(top = 12.dp), + HorizontalPager(pagerState) { selectedIndex -> + Column( + modifier = Modifier + .padding(start = 32.dp, end = 32.dp, bottom = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + val item = CarouselItems[selectedIndex] + Text( + text = item.title.resolveReference(), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 16.dp), + ) + Text( + text = item.subtitle.resolveReference(), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 12.dp), + ) + } + } + + Dots( + modifier = Modifier.align(Alignment.CenterHorizontally), + count = CarouselItems.size, + selectedIndex = pagerState.currentPage, ) } @@ -77,6 +97,28 @@ fun Wallet1ChooseOption( } } +@Composable +private fun Dots(count: Int, selectedIndex: Int, modifier: Modifier = Modifier) { + val generalColor = TangemTheme.colors.icon.informative + val selectedColor = TangemTheme.colors.icon.primary1 + + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + repeat(count) { index -> + val color by animateColorAsState(if (index == selectedIndex) selectedColor else generalColor) + + Canvas(Modifier.size(7.dp)) { + drawCircle( + color = color, + radius = size.width / 2f, + ) + } + } + } +} + @Preview(showBackground = true) @Composable private fun Preview() { diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt index c41f5bdff9..235a5a45ff 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt @@ -4,7 +4,7 @@ import androidx.compose.runtime.Stable import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.extensions.resourceReference @@ -34,7 +34,7 @@ import javax.inject.Inject @Suppress("LongParameterList") @Stable -@ComponentScoped +@ModelScoped internal class MultiWalletCreateWalletModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt index c87784f4ad..a84ab61103 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt @@ -3,7 +3,7 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.model import androidx.compose.runtime.Stable import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.card.repository.CardRepository @@ -39,7 +39,7 @@ import javax.inject.Inject @Suppress("LongParameterList") @Stable -@ComponentScoped +@ModelScoped internal class MultiWalletFinalizeModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, @@ -61,6 +61,7 @@ internal class MultiWalletFinalizeModel @Inject constructor( private val backupCardIds = backupServiceHolder.backupService.get()?.backupCardIds.orEmpty() private var walletHasBackupError = false + private var hasRing = false val uiState = _uiState.asStateFlow() val onBackFlow = MutableSharedFlow() @@ -152,6 +153,7 @@ internal class MultiWalletFinalizeModel @Inject constructor( val primaryCardBatchId = backupService.primaryCardBatchId ?: return val isRing = isRing(primaryCardBatchId) val iconScanRes = if (isRing) R.drawable.img_hand_scan_ring else null + if (isRing) hasRing = true tangemSdkManager.changeProductType(isRing) backupService.proceedBackup(iconScanRes = iconScanRes) { result -> @@ -178,6 +180,7 @@ internal class MultiWalletFinalizeModel @Inject constructor( val backupCardBatchId = backupService.backupCardsBatchIds.getOrNull(cardIndex) ?: return val isRing = isRing(backupCardBatchId) val iconScanRes = if (isRing) R.drawable.img_hand_scan_ring else null + if (isRing) hasRing = true tangemSdkManager.changeProductType(isRing) backupService.proceedBackup(iconScanRes = iconScanRes) { result -> @@ -218,7 +221,6 @@ internal class MultiWalletFinalizeModel @Inject constructor( private fun finishBackup() { modelScope.launch { val scanResponse = params.multiWalletState.value.currentScanResponse - val userWalletCreated = createUserWallet(scanResponse) val userWallet = when (params.parentParams.mode) { @@ -249,6 +251,10 @@ internal class MultiWalletFinalizeModel @Inject constructor( } } + if (hasRing) { + walletsRepository.setHasWalletsWithRing(userWallet.walletId) + } + // save user wallet for manage tokens screen params.multiWalletState.update { it.copy(resultUserWallet = userWallet) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/model/Wallet1ScanPrimaryModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/model/Wallet1ScanPrimaryModel.kt index 504e556fbc..621279a14b 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/model/Wallet1ScanPrimaryModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/model/Wallet1ScanPrimaryModel.kt @@ -2,7 +2,7 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.mod import androidx.compose.runtime.Stable import com.tangem.common.CompletionResult -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.common.util.cardTypesResolver @@ -15,7 +15,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @Stable -@ComponentScoped +@ModelScoped internal class Wallet1ScanPrimaryModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt index 93bc3e193c..7b75b80c4e 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt @@ -4,7 +4,7 @@ import androidx.compose.runtime.Stable import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.url.UrlOpener @@ -35,7 +35,7 @@ import javax.inject.Inject // ============================================= @Suppress("LongParameterList") @Stable -@ComponentScoped +@ModelScoped internal class MultiWalletSeedPhraseModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt index f64fd62c0d..0fc50e414e 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt @@ -7,7 +7,7 @@ import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll -import androidx.compose.material.OutlinedTextField +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -137,7 +137,6 @@ private fun PhraseBlock(state: MultiWalletSeedPhraseUM.Import, modifier: Modifie Column( modifier = modifier.fillMaxWidth(), ) { - // TODO migrate to material3 OutlinedTextField( modifier = Modifier .fillMaxWidth() diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt index f5ac73fd97..1099d8bcc1 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt @@ -84,8 +84,8 @@ private fun SegmentSeedBlock(state: MultiWalletSeedPhraseUM.GenerateSeedPhrase, Text( text = pluralStringResourceSafe( id = R.plurals.onboarding_seed_generate_words_count, - count = state.option.length, - state.option.length, + count = it.length, + it.length, ), modifier = Modifier .padding(vertical = TangemTheme.dimens.spacing10) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt index 1aee88709b..9054f73faa 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent import com.tangem.features.onboarding.v2.multiwallet.impl.DefaultOnboardingMultiWalletComponent @@ -30,7 +30,7 @@ internal interface ComponentModule { } @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface ModelModule { @Binds diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt index ad4786a6b7..8ca4e7faf4 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt @@ -1,13 +1,14 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.model import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ProductType import com.tangem.domain.wallets.usecase.GetCardImageUseCase import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent import com.tangem.features.onboarding.v2.multiwallet.impl.analytics.OnboardingEvent @@ -22,7 +23,7 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject -@ComponentScoped +@ModelScoped internal class OnboardingMultiWalletModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, @@ -116,8 +117,13 @@ internal class OnboardingMultiWalletModel @Inject constructor( card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup && scanResponse.primaryCard == null -> OnboardingMultiWalletState.Step.ScanPrimary - card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup -> - OnboardingMultiWalletState.Step.AddBackupDevice + card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup -> { + if (scanResponse.productType == ProductType.Wallet) { + OnboardingMultiWalletState.Step.ChooseBackupOption + } else { + OnboardingMultiWalletState.Step.AddBackupDevice + } + } card.wallets.isNotEmpty() && card.backupStatus?.isActive == true -> OnboardingMultiWalletState.Step.Finalize else -> diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Utils.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Utils.kt index 7c1d7416ed..1f4d1ee3a6 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Utils.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Utils.kt @@ -5,13 +5,13 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.features.onboarding.v2.impl.R fun screenTitleByStep(step: OnboardingMultiWalletState.Step): TextReference = when (step) { - OnboardingMultiWalletState.Step.CreateWallet -> - resourceReference(R.string.onboarding_create_wallet_header) + OnboardingMultiWalletState.Step.SeedPhrase, + OnboardingMultiWalletState.Step.CreateWallet, + -> resourceReference(R.string.onboarding_create_wallet_header) OnboardingMultiWalletState.Step.ScanPrimary, OnboardingMultiWalletState.Step.AddBackupDevice, - -> - resourceReference(R.string.onboarding_navbar_title_creating_backup) - OnboardingMultiWalletState.Step.Finalize -> - resourceReference(R.string.onboarding_button_finalize_backup) - else -> error("No title for the step") + -> resourceReference(R.string.onboarding_navbar_title_creating_backup) + OnboardingMultiWalletState.Step.ChooseBackupOption -> resourceReference(R.string.onboarding_getting_started) + OnboardingMultiWalletState.Step.Finalize -> resourceReference(R.string.onboarding_button_finalize_backup) + OnboardingMultiWalletState.Step.Done -> resourceReference(R.string.common_done) } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt index ae225a9f48..62bc6aaf7a 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt @@ -51,13 +51,13 @@ internal fun OnboardingMultiWallet( modifier = Modifier .padding(horizontal = 34.dp) .padding(top = 20.dp) - .weight(.4f) + .weight(.48f) .fillMaxWidth(), state = artworksState, ) Box( - modifier = Modifier.weight(.44f), + modifier = Modifier.weight(.52f), contentAlignment = Alignment.BottomStart, ) { childContent(Modifier) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/WalletArtwork.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/WalletArtwork.kt index 78d304117a..40eac45b30 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/WalletArtwork.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/WalletArtwork.kt @@ -2,10 +2,12 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.ui +import android.content.res.Configuration import androidx.compose.animation.core.Transition import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.updateTransition import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material3.Button import androidx.compose.material3.Text @@ -13,6 +15,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource @@ -106,7 +109,7 @@ fun WalletArtworks( } } - val circleColor = TangemTheme.colors.background.secondary + val circleColor = TangemTheme.colors.button.secondary Canvas( modifier = Modifier @@ -252,6 +255,7 @@ private fun WalletCard(url: String?, modifier: Modifier = Modifier) { placeholder = painterResource(R.drawable.card_placeholder_black), error = painterResource(R.drawable.card_placeholder_black), fallback = painterResource(R.drawable.card_placeholder_black), + contentScale = ContentScale.Fit, contentDescription = null, ) } @@ -261,16 +265,16 @@ private fun WalletArtworksState.toTransitionSetState(maxWidthDp: Float, maxHeigh WalletArtworksState.Hidden -> listOf() is WalletArtworksState.Folded -> listOf( CardsTransitionState( - WalletCardTransitionState(), - WalletCardTransitionState(), - WalletCardTransitionState(), + WalletCardTransitionState(alpha = 1f), + WalletCardTransitionState(alpha = 0f), + WalletCardTransitionState(alpha = 0f), ), ) is WalletArtworksState.Stack -> listOf( CardsTransitionState( - walletCard1 = WalletCardTransitionState(), - walletCard2 = WalletCardTransitionState(), - walletCard3 = WalletCardTransitionState(), + WalletCardTransitionState(alpha = 1f), + WalletCardTransitionState(alpha = 0f), + WalletCardTransitionState(alpha = 0f), ), CardsTransitionState( walletCard1 = WalletCardTransitionState().copy( @@ -451,11 +455,14 @@ private fun WalletArtworksState.toTransitionSetState(maxWidthDp: Float, maxHeigh } @Preview(showBackground = true, widthDp = 360, heightDp = 640) +@Preview(showBackground = true, widthDp = 360, heightDp = 640, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun Preview() { TangemThemePreview { Box( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .background(TangemTheme.colors.background.primary) + .fillMaxSize(), contentAlignment = Alignment.Center, ) { var state: WalletArtworksState by remember { mutableStateOf(WalletArtworksState.Folded) } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/api/OnboardingNoteComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/api/OnboardingNoteComponent.kt new file mode 100644 index 0000000000..3bbab7074a --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/api/OnboardingNoteComponent.kt @@ -0,0 +1,17 @@ +package com.tangem.features.onboarding.v2.note.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.features.onboarding.v2.TitleProvider + +interface OnboardingNoteComponent : ComposableContentComponent { + + data class Params( + val titleProvider: TitleProvider, + val scanResponse: ScanResponse, + val onDone: () -> Unit, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/DefaultOnboardingNoteComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/DefaultOnboardingNoteComponent.kt new file mode 100644 index 0000000000..3dacc2bdc0 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/DefaultOnboardingNoteComponent.kt @@ -0,0 +1,28 @@ +package com.tangem.features.onboarding.v2.note.impl + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.features.onboarding.v2.note.api.OnboardingNoteComponent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultOnboardingNoteComponent @AssistedInject constructor( + @Assisted context: AppComponentContext, + @Assisted val params: OnboardingNoteComponent.Params, +) : OnboardingNoteComponent, AppComponentContext by context { + + @Composable + override fun Content(modifier: Modifier) { + // TODO [REDACTED_TASK_KEY] + } + + @AssistedFactory + interface Factory : OnboardingNoteComponent.Factory { + override fun create( + context: AppComponentContext, + params: OnboardingNoteComponent.Params, + ): DefaultOnboardingNoteComponent + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/di/ComponentModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/di/ComponentModule.kt new file mode 100644 index 0000000000..49d9a5b659 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/di/ComponentModule.kt @@ -0,0 +1,18 @@ +package com.tangem.features.onboarding.v2.note.impl.di + +import com.tangem.features.onboarding.v2.note.api.OnboardingNoteComponent +import com.tangem.features.onboarding.v2.note.impl.DefaultOnboardingNoteComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface ComponentModule { + + @Binds + @Singleton + fun bindComponent(factory: DefaultOnboardingNoteComponent.Factory): OnboardingNoteComponent.Factory +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt index 9ab73f41eb..b36dcef18f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt @@ -3,13 +3,13 @@ package com.tangem.features.onboarding.v2.visa.impl import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children -import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.slide -import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.extensions.compose.stack.Children +import com.arkivanov.decompose.extensions.compose.stack.animation.slide +import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation +import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.stack.* import com.arkivanov.decompose.value.Value -import com.arkivanov.decompose.value.observe +import com.arkivanov.decompose.value.subscribe import com.arkivanov.essenty.instancekeeper.getOrCreateSimple import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.childByContext @@ -85,7 +85,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( } // sets title and stepper value - childStack.observe(lifecycle) { stack -> + childStack.subscribe(lifecycle) { stack -> val currentRoute = stack.active.configuration params.titleProvider.changeTitle(currentRoute.screenTitle()) model.updateStepForNewRoute(currentRoute) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt index 2f56bd8ddb..086d02041d 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt @@ -4,7 +4,7 @@ import androidx.compose.runtime.Stable import androidx.compose.ui.text.input.TextFieldValue import com.tangem.common.CompletionResult import com.tangem.common.extensions.toHexString -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.visa.model.VisaCardActivationStatus @@ -26,7 +26,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @Stable -@ComponentScoped +@ModelScoped internal class OnboardingVisaAccessCodeModel @Inject constructor( paramsContainer: ParamsContainer, visaActivationRepositoryFactory: VisaActivationRepository.Factory, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/model/OnboardingVisaApproveModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/model/OnboardingVisaApproveModel.kt index 245dbb83cb..396a8039b2 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/model/OnboardingVisaApproveModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/model/OnboardingVisaApproveModel.kt @@ -3,7 +3,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.approve.model import androidx.compose.runtime.Stable import com.tangem.common.CompletionResult import com.tangem.common.extensions.toHexString -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.visa.model.VisaCardId @@ -21,7 +21,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @Stable -@ComponentScoped +@ModelScoped internal class OnboardingVisaApproveModel @Inject constructor( paramsContainer: ParamsContainer, visaActivationRepositoryFactory: VisaActivationRepository.Factory, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/choosewallet/model/OnboardingVisaChooseWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/choosewallet/model/OnboardingVisaChooseWalletModel.kt index 4f6629cae9..381cfc11aa 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/choosewallet/model/OnboardingVisaChooseWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/choosewallet/model/OnboardingVisaChooseWalletModel.kt @@ -1,7 +1,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.model import androidx.compose.runtime.Stable -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.ui.extensions.TextReference import com.tangem.features.onboarding.v2.impl.R @@ -18,7 +18,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @Stable -@ComponentScoped +@ModelScoped internal class OnboardingVisaChooseWalletModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, ) : Model() { diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/model/OnboardingVisaInProgressModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/model/OnboardingVisaInProgressModel.kt index 48ac5a404d..3d79bce2c1 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/model/OnboardingVisaInProgressModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/model/OnboardingVisaInProgressModel.kt @@ -2,7 +2,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.inprogress.model import androidx.compose.runtime.Stable import com.tangem.common.extensions.toHexString -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.datasource.local.visa.VisaAuthTokenStorage @@ -27,7 +27,7 @@ import javax.inject.Inject @Suppress("LongParameterList") @Stable -@ComponentScoped +@ModelScoped internal class OnboardingVisaInProgressModel @Inject constructor( paramsContainer: ParamsContainer, visaActivationRepositoryFactory: VisaActivationRepository.Factory, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/model/OnboardingVisaOtherWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/model/OnboardingVisaOtherWalletModel.kt index 887cd4759f..53c043fbea 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/model/OnboardingVisaOtherWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/model/OnboardingVisaOtherWalletModel.kt @@ -2,7 +2,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.model import androidx.compose.runtime.Stable import com.tangem.common.extensions.toHexString -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.share.ShareManager @@ -22,7 +22,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @Stable -@ComponentScoped +@ModelScoped internal class OnboardingVisaOtherWalletModel @Inject constructor( paramsContainer: ParamsContainer, visaActivationRepositoryFactory: VisaActivationRepository.Factory, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/OnboardingVisaPinCodeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/OnboardingVisaPinCodeModel.kt index deae6ff93c..0b02574d50 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/OnboardingVisaPinCodeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/OnboardingVisaPinCodeModel.kt @@ -2,7 +2,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.pincode.model import androidx.compose.runtime.Stable import com.tangem.common.extensions.toHexString -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.datasource.local.visa.VisaAuthTokenStorage @@ -26,7 +26,7 @@ import kotlinx.coroutines.withContext import javax.inject.Inject @Stable -@ComponentScoped +@ModelScoped @Suppress("LongParameterList") internal class OnboardingVisaPinCodeModel @Inject constructor( paramsContainer: ParamsContainer, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/model/OnboardingVisaWelcomeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/model/OnboardingVisaWelcomeModel.kt index 526e51806f..82a7092b99 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/model/OnboardingVisaWelcomeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/model/OnboardingVisaWelcomeModel.kt @@ -2,7 +2,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.welcome.model import com.tangem.common.CompletionResult import com.tangem.common.extensions.toHexString -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.visa.model.VisaCardId @@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject -@ComponentScoped +@ModelScoped internal class OnboardingVisaWelcomeModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/di/ComponentModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/di/ComponentModule.kt index 15349b614b..d4421b9169 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/di/ComponentModule.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/di/ComponentModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onboarding.v2.visa.impl.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onboarding.v2.visa.api.OnboardingVisaComponent import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent @@ -30,7 +30,7 @@ internal interface ComponentModule { } @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface ModelModule { @Binds diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/model/OnboardingVisaModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/model/OnboardingVisaModel.kt index ca2b727f7c..5e98a81f2b 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/model/OnboardingVisaModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/model/OnboardingVisaModel.kt @@ -6,7 +6,7 @@ import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.pop import com.arkivanov.decompose.router.stack.push import com.arkivanov.decompose.router.stack.pushNew -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.common.visa.VisaUtilities @@ -31,7 +31,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @Stable -@ComponentScoped +@ModelScoped internal class OnboardingVisaModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt index bdeb4fe70e..e0cab80df9 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt @@ -6,8 +6,8 @@ import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Icon -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt index bc6e59c4dd..7c03fef7bc 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt @@ -7,8 +7,8 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.OutlinedTextField -import androidx.compose.material.Text +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/PassphraseInfoBottomSheet.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/PassphraseInfoBottomSheet.kt index cf5f16d4d0..cddeb6277c 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/PassphraseInfoBottomSheet.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/PassphraseInfoBottomSheet.kt @@ -5,8 +5,8 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.material.Icon -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt index e5d110febd..2f7f864d6e 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt @@ -5,7 +5,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Text +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt index e0ee2d086e..52806b14c3 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt @@ -5,7 +5,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material.Text +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt index 0e1ea2888c..2ff2e6a708 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt @@ -5,7 +5,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.slot.childSlot import com.arkivanov.decompose.router.slot.dismiss import com.tangem.core.decompose.context.AppComponentContext diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/di/OnrampConfirmResidencyModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/di/OnrampConfirmResidencyModelModule.kt index ab79a46e1c..b58e525556 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/di/OnrampConfirmResidencyModelModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/di/OnrampConfirmResidencyModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onramp.confirmresidency.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onramp.confirmresidency.model.ConfirmResidencyModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface OnrampConfirmResidencyModelModule { @Binds @IntoMap diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt index 270c3e6f73..3466152b67 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt @@ -3,7 +3,7 @@ package com.tangem.features.onramp.confirmresidency.model import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router @@ -20,7 +20,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import javax.inject.Inject -@ComponentScoped +@ModelScoped internal class ConfirmResidencyModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val analyticsEventHandler: AnalyticsEventHandler, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/DefaultHotCryptoComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/DefaultHotCryptoComponent.kt index 4c6efe7cec..f284662276 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/DefaultHotCryptoComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/DefaultHotCryptoComponent.kt @@ -5,7 +5,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.slot.childSlot import com.arkivanov.decompose.router.slot.dismiss import com.tangem.core.decompose.context.AppComponentContext diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/di/HotCryptoModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/di/HotCryptoModelModule.kt index 7757a30590..7ed81b78db 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/di/HotCryptoModelModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/di/HotCryptoModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onramp.hottokens.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onramp.hottokens.model.HotCryptoModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface HotCryptoModelModule { @Binds diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt index 29a5b07946..be622c09d1 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt @@ -4,7 +4,7 @@ import arrow.core.getOrElse import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate import com.arkivanov.decompose.router.slot.dismiss -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.components.token.state.TokenItemState @@ -40,7 +40,7 @@ import javax.inject.Inject * [REDACTED_AUTHOR] */ -@ComponentScoped +@ModelScoped internal class HotCryptoModel @Inject constructor( paramsContainer: ParamsContainer, getHotCryptoUseCase: GetHotCryptoUseCase, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/di/OnrampAddToPortfolioModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/di/OnrampAddToPortfolioModelModule.kt index 086a66b0d0..4db40ffeca 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/di/OnrampAddToPortfolioModelModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/di/OnrampAddToPortfolioModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onramp.hottokens.portfolio.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onramp.hottokens.portfolio.model.OnrampAddToPortfolioModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface OnrampAddToPortfolioModelModule { @Binds diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddToPortfolioModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddToPortfolioModel.kt index 9ca6e59406..ed8f3c543a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddToPortfolioModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddToPortfolioModel.kt @@ -1,7 +1,7 @@ package com.tangem.features.onramp.hottokens.portfolio.model import arrow.core.getOrElse -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.card.DerivePublicKeysUseCase @@ -29,7 +29,7 @@ import javax.inject.Inject * [REDACTED_AUTHOR] */ -@ComponentScoped +@ModelScoped internal class OnrampAddToPortfolioModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt index 5164c91d5b..f6c03525c1 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt @@ -5,7 +5,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.slot.childSlot import com.arkivanov.decompose.router.slot.dismiss import com.tangem.core.decompose.context.AppComponentContext diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModelModule.kt index 99826bae5d..f3bbdba606 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModelModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onramp.main.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onramp.main.model.OnrampMainComponentModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface OnrampMainComponentModelModule { @Binds diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/DefaultSelectProviderComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/DefaultSelectProviderComponent.kt index d0bcd4a3d0..df819c4c7f 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/DefaultSelectProviderComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/DefaultSelectProviderComponent.kt @@ -8,7 +8,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.slot.childSlot import com.arkivanov.decompose.router.slot.dismiss import com.tangem.core.decompose.context.AppComponentContext diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/di/SelectProviderModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/di/SelectProviderModelModule.kt index 812d6bd45d..3719bf0fae 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/di/SelectProviderModelModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/di/SelectProviderModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onramp.providers.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onramp.providers.model.SelectProviderModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface SelectProviderModelModule { @Binds diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt index bc077ed6a3..ea142aa30f 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt @@ -4,7 +4,7 @@ import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate import com.arkivanov.decompose.router.slot.dismiss import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.extensions.resourceReference @@ -40,7 +40,7 @@ import java.math.BigDecimal import javax.inject.Inject @Suppress("LongParameterList") -@ComponentScoped +@ModelScoped internal class SelectProviderModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val analyticsEventHandler: AnalyticsEventHandler, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/di/OnrampRedirectModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/di/OnrampRedirectModelModule.kt index 21547db511..619e70ca91 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/di/OnrampRedirectModelModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/di/OnrampRedirectModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onramp.redirect.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onramp.redirect.model.OnrampRedirectModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface OnrampRedirectModelModule { @Binds diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt index 02047a8939..48804d46be 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt @@ -5,9 +5,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children -import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.extensions.compose.stack.Children +import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation +import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.childStack import com.arkivanov.decompose.router.stack.pop diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/di/OnrampSelectCountryModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/di/OnrampSelectCountryModelModule.kt index 1230e7174d..8be0024799 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/di/OnrampSelectCountryModelModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/di/OnrampSelectCountryModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onramp.selectcountry.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onramp.selectcountry.model.OnrampSelectCountryModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface OnrampSelectCountryModelModule { @Binds diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt index 94296a59e1..e7a8f8dedf 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt @@ -1,7 +1,7 @@ package com.tangem.features.onramp.selectcountry.model import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.components.fields.entity.SearchBarUM @@ -35,7 +35,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @Suppress("LongParameterList") -@ComponentScoped +@ModelScoped internal class OnrampSelectCountryModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val analyticsEventHandler: AnalyticsEventHandler, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyModelModule.kt index 3541b9cb45..f699c1205c 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyModelModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onramp.selectcurrency.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onramp.selectcurrency.model.OnrampSelectCurrencyModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface OnrampSelectCurrencyModelModule { @Binds diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt index a297ae455a..93d64b7bca 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt @@ -1,7 +1,7 @@ package com.tangem.features.onramp.selectcurrency.model import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.components.fields.entity.SearchBarUM @@ -35,7 +35,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @Suppress("LongParameterList") -@ComponentScoped +@ModelScoped internal class OnrampSelectCurrencyModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val analyticsEventHandler: AnalyticsEventHandler, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampOperationModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampOperationModelModule.kt index 9c9eb054d2..7488094564 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampOperationModelModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampOperationModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onramp.selecttoken.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onramp.selecttoken.model.OnrampOperationModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface OnrampOperationModelModule { @Binds diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/model/OnrampOperationModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/model/OnrampOperationModel.kt index b0555c7c63..770e77d257 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/model/OnrampOperationModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/model/OnrampOperationModel.kt @@ -6,7 +6,7 @@ import com.tangem.common.ui.alerts.models.AlertDemoModeUM import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.ui.UiMessageSender @@ -33,7 +33,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @Suppress("LongParameterList") -@ComponentScoped +@ModelScoped internal class OnrampOperationModel @Inject constructor( paramsContainer: ParamsContainer, getWalletsUseCase: GetWalletsUseCase, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/DefaultOnrampSettingsComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/DefaultOnrampSettingsComponent.kt index a3c7234332..941d543d82 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/DefaultOnrampSettingsComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/DefaultOnrampSettingsComponent.kt @@ -6,7 +6,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.slot.childSlot import com.arkivanov.decompose.router.slot.dismiss import com.tangem.core.decompose.context.AppComponentContext diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/di/OnrampSettingsModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/di/OnrampSettingsModelModule.kt index f62678e7ee..8b90098a7f 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/di/OnrampSettingsModelModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/di/OnrampSettingsModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onramp.settings.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onramp.settings.model.OnrampSettingsModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface OnrampSettingsModelModule { @Binds diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt index a67426d614..95ed639744 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt @@ -4,7 +4,7 @@ import arrow.core.Either import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.onramp.GetOnrampCountryUseCase @@ -21,7 +21,7 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.* import javax.inject.Inject -@ComponentScoped +@ModelScoped internal class OnrampSettingsModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val analyticsEventHandler: AnalyticsEventHandler, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModelModule.kt index ba6f6664fd..59b1e6f64a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModelModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onramp.success.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onramp.success.model.OnrampSuccessComponentModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface OnrampSuccessComponentModelModule { @Binds @IntoMap diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsModelModule.kt index 6bf7c67133..b68b705703 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsModelModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onramp.swap.availablepairs.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onramp.swap.availablepairs.model.AvailableSwapPairsModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface AvailableSwapPairsModelModule { @Binds diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensModelModule.kt index f7e0741156..01b1201354 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensModelModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onramp.swap.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onramp.swap.model.SwapSelectTokensModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface SwapSelectTokensModelModule { @Binds diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/di/OnrampTokenListModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/di/OnrampTokenListModelModule.kt index 7977384f55..0f35146dfe 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/di/OnrampTokenListModelModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/di/OnrampTokenListModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.features.onramp.tokenlist.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onramp.tokenlist.model.OnrampTokenListModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface OnrampTokenListModelModule { @Binds diff --git a/features/push-notifications/api/build.gradle.kts b/features/push-notifications/api/build.gradle.kts index 8fd2b5a11b..3366d4511c 100644 --- a/features/push-notifications/api/build.gradle.kts +++ b/features/push-notifications/api/build.gradle.kts @@ -9,10 +9,11 @@ android { } dependencies { - /** AndroidX */ implementation(deps.androidx.fragment.ktx) /** Core */ + implementation(projects.core.decompose) + implementation(projects.core.ui) implementation(projects.core.analytics.models) } \ No newline at end of file diff --git a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsComponent.kt b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsComponent.kt new file mode 100644 index 0000000000..515492abe3 --- /dev/null +++ b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsComponent.kt @@ -0,0 +1,9 @@ +package com.tangem.features.pushnotifications.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent + +interface PushNotificationsComponent : ComposableContentComponent { + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/navigation/PushNotificationsRouter.kt b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/navigation/PushNotificationsRouter.kt deleted file mode 100644 index 6881437671..0000000000 --- a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/navigation/PushNotificationsRouter.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.features.pushnotifications.api.navigation - -import androidx.fragment.app.Fragment - -interface PushNotificationsRouter { - - fun entryFragment(): Fragment -} \ No newline at end of file diff --git a/features/push-notifications/impl/build.gradle.kts b/features/push-notifications/impl/build.gradle.kts index de3b57819b..184fccb455 100644 --- a/features/push-notifications/impl/build.gradle.kts +++ b/features/push-notifications/impl/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { implementation(deps.kotlin.immutable.collections) /** Core modules */ + implementation(projects.core.decompose) implementation(projects.core.ui) implementation(projects.core.configToggles) implementation(projects.core.navigation) diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/DefaultPushNotificationsComponent.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/DefaultPushNotificationsComponent.kt new file mode 100644 index 0000000000..d2ba519029 --- /dev/null +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/DefaultPushNotificationsComponent.kt @@ -0,0 +1,42 @@ +package com.tangem.features.pushnotifications.impl + +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.components.NavigationBar3ButtonsScrim +import com.tangem.core.ui.utils.findActivity +import com.tangem.features.pushnotifications.api.PushNotificationsComponent +import com.tangem.features.pushnotifications.impl.model.PushNotificationsModel +import com.tangem.features.pushnotifications.impl.presentation.ui.PushNotificationsScreen +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultPushNotificationsComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: Unit, +) : PushNotificationsComponent, AppComponentContext by appComponentContext { + + private val model: PushNotificationsModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val activity = LocalContext.current.findActivity() + BackHandler(onBack = { activity.finish() }) + NavigationBar3ButtonsScrim() + PushNotificationsScreen( + onRequest = model::onRequest, + onNeverRequest = model::onNeverRequest, + onAllowPermission = model::onAllowPermission, + onDenyPermission = model::onDenyPermission, + ) + } + + @AssistedFactory + interface Factory : PushNotificationsComponent.Factory { + override fun create(context: AppComponentContext, params: Unit): DefaultPushNotificationsComponent + } +} \ No newline at end of file diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/PushNotificationsFragment.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/PushNotificationsFragment.kt deleted file mode 100644 index c7110e411f..0000000000 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/PushNotificationsFragment.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.features.pushnotifications.impl - -import androidx.activity.compose.BackHandler -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.fragment.app.viewModels -import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.components.NavigationBar3ButtonsScrim -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.features.pushnotifications.impl.presentation.ui.PushNotificationsScreen -import com.tangem.features.pushnotifications.impl.presentation.viewmodel.PushNotificationViewModel -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -@AndroidEntryPoint -internal class PushNotificationsFragment : ComposeFragment() { - - @Inject - override lateinit var uiDependencies: UiDependencies - - private val viewModel by viewModels() - - @Composable - override fun ScreenContent(modifier: Modifier) { - BackHandler(onBack = requireActivity()::finish) - NavigationBar3ButtonsScrim() - PushNotificationsScreen( - onRequest = viewModel::onRequest, - onNeverRequest = viewModel::onNeverRequest, - onAllowPermission = viewModel::onAllowPermission, - onDenyPermission = viewModel::onDenyPermission, - ) - } - - companion object { - /** Create push notifications fragment instance */ - fun create(): PushNotificationsFragment = PushNotificationsFragment() - } -} \ No newline at end of file diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/di/PushNotificationsModule.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/di/PushNotificationsModule.kt index b568880483..edbd3dc188 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/di/PushNotificationsModule.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/di/PushNotificationsModule.kt @@ -1,24 +1,25 @@ package com.tangem.features.pushnotifications.impl.di -import com.tangem.common.routing.AppRouter -import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter -import com.tangem.features.pushnotifications.impl.navigation.DefaultPushNotificationsRouter +import com.tangem.core.decompose.model.Model +import com.tangem.features.pushnotifications.api.PushNotificationsComponent +import com.tangem.features.pushnotifications.impl.DefaultPushNotificationsComponent +import com.tangem.features.pushnotifications.impl.model.PushNotificationsModel +import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityComponent -import dagger.hilt.android.scopes.ActivityScoped +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap -/** - * DI module provides implementation of [PushNotificationsRouter] - */ @Module -@InstallIn(ActivityComponent::class) -object PushNotificationsModule { +@InstallIn(SingletonComponent::class) +internal interface PushNotificationsModule { - @Provides - @ActivityScoped - fun provideDisclaimerRouter(appRouter: AppRouter): PushNotificationsRouter { - return DefaultPushNotificationsRouter(appRouter) - } + @Binds + fun bindComponentFactory(impl: DefaultPushNotificationsComponent.Factory): PushNotificationsComponent.Factory + + @Binds + @IntoMap + @ClassKey(PushNotificationsModel::class) + fun bindModel(model: PushNotificationsModel): Model } \ No newline at end of file diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationsClickIntents.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsClickIntents.kt similarity index 67% rename from features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationsClickIntents.kt rename to features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsClickIntents.kt index 63100190b7..99b02a379c 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationsClickIntents.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsClickIntents.kt @@ -1,4 +1,4 @@ -package com.tangem.features.pushnotifications.impl.presentation.viewmodel +package com.tangem.features.pushnotifications.impl.model internal interface PushNotificationsClickIntents { fun onRequest() diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationViewModel.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt similarity index 70% rename from features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationViewModel.kt rename to features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt index b760936b5b..fb23fe3946 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationViewModel.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt @@ -1,26 +1,29 @@ -package com.tangem.features.pushnotifications.impl.presentation.viewmodel +package com.tangem.features.pushnotifications.impl.model -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.compose.runtime.Stable +import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model import com.tangem.domain.settings.NeverRequestPermissionUseCase import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION -import com.tangem.features.pushnotifications.impl.navigation.DefaultPushNotificationsRouter -import dagger.hilt.android.lifecycle.HiltViewModel +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.launch import javax.inject.Inject -@Suppress("LongParameterList") -@HiltViewModel -internal class PushNotificationViewModel @Inject constructor( +@Stable +@ModelScoped +class PushNotificationsModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase, private val neverToInitiallyAskPermissionUseCase: NeverToInitiallyAskPermissionUseCase, - private val router: DefaultPushNotificationsRouter, + private val appRouter: AppRouter, private val analyticHandler: AnalyticsEventHandler, -) : ViewModel(), PushNotificationsClickIntents { +) : Model(), PushNotificationsClickIntents { override fun onRequest() { analyticHandler.send( @@ -32,10 +35,10 @@ internal class PushNotificationViewModel @Inject constructor( analyticHandler.send( PushNotificationAnalyticEvents.ButtonLater(AnalyticsParam.ScreensSources.Stories), ) - viewModelScope.launch { + modelScope.launch { neverRequestPermissionUseCase(PUSH_PERMISSION) neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) - router.openHome() + appRouter.push(AppRoute.Home) } } @@ -43,10 +46,10 @@ internal class PushNotificationViewModel @Inject constructor( analyticHandler.send( PushNotificationAnalyticEvents.PermissionStatus(isAllowed = true), ) - viewModelScope.launch { + modelScope.launch { neverRequestPermissionUseCase(PUSH_PERMISSION) neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) - router.openHome() + appRouter.push(AppRoute.Home) } } @@ -54,10 +57,10 @@ internal class PushNotificationViewModel @Inject constructor( analyticHandler.send( PushNotificationAnalyticEvents.PermissionStatus(isAllowed = false), ) - viewModelScope.launch { + modelScope.launch { neverRequestPermissionUseCase(PUSH_PERMISSION) neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) - router.openHome() + appRouter.push(AppRoute.Home) } } } \ No newline at end of file diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/navigation/DefaultPushNotificationsRouter.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/navigation/DefaultPushNotificationsRouter.kt deleted file mode 100644 index b2536cfe47..0000000000 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/navigation/DefaultPushNotificationsRouter.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.features.pushnotifications.impl.navigation - -import androidx.fragment.app.Fragment -import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.AppRouter -import com.tangem.features.pushnotifications.impl.PushNotificationsFragment -import javax.inject.Inject - -internal class DefaultPushNotificationsRouter @Inject constructor( - private val appRouter: AppRouter, -) : InnerPushNotificationsRouter { - - override fun entryFragment(): Fragment = PushNotificationsFragment.create() - - override fun openHome() { - appRouter.push(AppRoute.Home) - } -} \ No newline at end of file diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/navigation/InnerPushNotificationsRouter.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/navigation/InnerPushNotificationsRouter.kt deleted file mode 100644 index 20050eca1e..0000000000 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/navigation/InnerPushNotificationsRouter.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.features.pushnotifications.impl.navigation - -import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter - -interface InnerPushNotificationsRouter : PushNotificationsRouter { - - fun openHome() -} \ No newline at end of file diff --git a/features/qr-scanning/api/build.gradle.kts b/features/qr-scanning/api/build.gradle.kts index d88f0c6f03..5508ff8910 100644 --- a/features/qr-scanning/api/build.gradle.kts +++ b/features/qr-scanning/api/build.gradle.kts @@ -9,6 +9,12 @@ android { } dependencies { + /** Domain models */ + implementation(projects.domain.qrScanning.models) + + /** Core */ + implementation(projects.core.decompose) + implementation(projects.core.ui) /** AndroidX */ implementation(deps.androidx.fragment.ktx) diff --git a/features/qr-scanning/api/src/main/java/com/tangem/feature/qrscanning/QrScanningComponent.kt b/features/qr-scanning/api/src/main/java/com/tangem/feature/qrscanning/QrScanningComponent.kt new file mode 100644 index 0000000000..4789912572 --- /dev/null +++ b/features/qr-scanning/api/src/main/java/com/tangem/feature/qrscanning/QrScanningComponent.kt @@ -0,0 +1,15 @@ +package com.tangem.feature.qrscanning + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.qrscanning.models.SourceType + +interface QrScanningComponent : ComposableContentComponent { + + data class Params( + val source: SourceType, + val networkName: String? = null, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/qr-scanning/api/src/main/java/com/tangem/feature/qrscanning/QrScanningRouter.kt b/features/qr-scanning/api/src/main/java/com/tangem/feature/qrscanning/QrScanningRouter.kt deleted file mode 100644 index 049e039209..0000000000 --- a/features/qr-scanning/api/src/main/java/com/tangem/feature/qrscanning/QrScanningRouter.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.feature.qrscanning - -import androidx.fragment.app.Fragment - -interface QrScanningRouter { - - fun getEntryFragment(): Fragment -} \ No newline at end of file diff --git a/features/qr-scanning/impl/build.gradle.kts b/features/qr-scanning/impl/build.gradle.kts index f8bb506a61..5e1a8a4c8f 100644 --- a/features/qr-scanning/impl/build.gradle.kts +++ b/features/qr-scanning/impl/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { /** Core */ implementation(projects.core.ui) + implementation(projects.core.decompose) implementation(projects.core.utils) implementation(projects.core.navigation) implementation(projects.common.routing) diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/DefaultQrScanningComponent.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/DefaultQrScanningComponent.kt new file mode 100644 index 0000000000..2e00a6910d --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/DefaultQrScanningComponent.kt @@ -0,0 +1,142 @@ +package com.tangem.feature.qrscanning + +import android.Manifest +import android.content.pm.PackageManager +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.core.content.ContextCompat +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LifecycleEventEffect +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.arkivanov.essenty.lifecycle.doOnDestroy +import com.google.mlkit.vision.common.InputImage +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.components.SystemBarsIconsDisposable +import com.tangem.feature.qrscanning.inner.MLKitBarcodeAnalyzer +import com.tangem.feature.qrscanning.model.QrScanningModel +import com.tangem.feature.qrscanning.presentation.QrScanningContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.delay +import timber.log.Timber +import java.io.IOException +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class DefaultQrScanningComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: QrScanningComponent.Params, +) : QrScanningComponent, AppComponentContext by appComponentContext { + + private val model: QrScanningModel = getOrCreateModel(params) + + private val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor() + // Camera requires its own analyzer instance due to flow of frames needed to be analyzed. + // Each new frame can cancel previous analysis e.i. image from the gallery can be skipped. + private val cameraAnalyzer: MLKitBarcodeAnalyzer by lazy(LazyThreadSafetyMode.NONE) { + MLKitBarcodeAnalyzer(model::onQrScanned) + } + private val analyzer: MLKitBarcodeAnalyzer by lazy(LazyThreadSafetyMode.NONE) { + MLKitBarcodeAnalyzer(model::onQrScanned) + } + + init { + lifecycle.doOnDestroy { cameraExecutor.shutdown() } + } + + @Composable + override fun Content(modifier: Modifier) { + val context = LocalContext.current + + val cameraPermissionLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (isGranted.not()) { + model.onCameraDeniedState() + } + } + + val galleryLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { + val selectedImage = it ?: Uri.EMPTY + if (selectedImage != Uri.EMPTY) { + val mimeType = context.contentResolver.getType(selectedImage) + if (mimeType.isImageMimeType()) { + try { + val image = InputImage.fromFilePath(context, selectedImage) + analyzer.analyze(image) + } catch (e: IOException) { + Timber.e(e, "Unable to get image $selectedImage from gallery") + } + } + } + } + + LaunchedEffect(Unit) { + model.launchGallery.collect { + galleryLauncher.launch(GALLERY_IMAGE_FILTER) + delay(timeMillis = 2000) + } + } + + LifecycleEventEffect( + event = Lifecycle.Event.ON_CREATE, + ) { + if ( + ContextCompat.checkSelfPermission( + context, + Manifest.permission.CAMERA, + ) == PackageManager.PERMISSION_DENIED + ) { + cameraPermissionLauncher.launch(Manifest.permission.CAMERA) + } + } + + LifecycleEventEffect( + event = Lifecycle.Event.ON_RESUME, + ) { + if (ContextCompat.checkSelfPermission( + context, + Manifest.permission.CAMERA, + ) == PackageManager.PERMISSION_GRANTED + ) { + model.onDismissBottomSheetState() + } + } + + ScreenContent(modifier) + } + + @Suppress("UnusedPrivateMember") + @Composable + private fun ScreenContent(modifier: Modifier = Modifier) { + SystemBarsIconsDisposable(darkIcons = false) + + QrScanningContent( + executor = { cameraExecutor }, + analyzer = { cameraAnalyzer }, + uiState = model.uiState.collectAsStateWithLifecycle().value, + ) + } + + private fun String?.isImageMimeType() = this?.startsWith(prefix = "$IMAGE_MIME_TYPE/") == true + + @AssistedFactory + interface Factory : QrScanningComponent.Factory { + override fun create( + context: AppComponentContext, + params: QrScanningComponent.Params, + ): DefaultQrScanningComponent + } + + companion object { + + private const val IMAGE_MIME_TYPE = "image" + private const val GALLERY_IMAGE_FILTER = "$IMAGE_MIME_TYPE/*" + } +} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt deleted file mode 100644 index 787c7bdfe9..0000000000 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt +++ /dev/null @@ -1,158 +0,0 @@ -package com.tangem.feature.qrscanning - -import android.Manifest -import android.content.pm.PackageManager -import android.net.Uri -import android.os.Bundle -import android.view.View -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.core.content.ContextCompat -import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import com.google.mlkit.vision.common.InputImage -import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.components.SystemBarsIconsDisposable -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.feature.qrscanning.inner.MLKitBarcodeAnalyzer -import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter -import com.tangem.feature.qrscanning.presentation.QrScanningContent -import com.tangem.feature.qrscanning.viewmodel.QrScanningViewModel -import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import timber.log.Timber -import java.io.IOException -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors -import javax.inject.Inject -import kotlin.properties.Delegates - -@AndroidEntryPoint -internal class QrScanningFragment : ComposeFragment() { - - @Inject - override lateinit var uiDependencies: UiDependencies - - @Inject - lateinit var router: QrScanningRouter - - private val innerRouter: QrScanningInnerRouter - get() = requireNotNull(router as? QrScanningInnerRouter) { - "innerRouter should be instance of QrScanningInnerRouter" - } - - private val viewModel by viewModels() - - private var cameraExecutor: ExecutorService by Delegates.notNull() - // Camera requires its own analyzer instance due to flow of frames needed to be analyzed. - // Each new frame can cancel previous analysis e.i. image from the gallery can be skipped. - private val cameraAnalyzer: MLKitBarcodeAnalyzer by lazy(LazyThreadSafetyMode.NONE) { - MLKitBarcodeAnalyzer(viewModel::onQrScanned) - } - private val analyzer: MLKitBarcodeAnalyzer by lazy(LazyThreadSafetyMode.NONE) { - MLKitBarcodeAnalyzer(viewModel::onQrScanned) - } - - private val cameraPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { - if (!it) viewModel.onCameraDeniedState() - } - private val galleryLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { - val selectedImage = it ?: Uri.EMPTY - if (selectedImage != Uri.EMPTY) { - val mimeType = requireContext().contentResolver.getType(selectedImage) - if (mimeType.isImageMimeType()) { - try { - val image = InputImage.fromFilePath(requireContext(), selectedImage) - analyzer.analyze(image) - } catch (e: IOException) { - Timber.e(e, "Unable to get image $selectedImage from gallery") - } - } - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - viewModel.setRouter(innerRouter) - cameraExecutor = Executors.newSingleThreadExecutor() - requestCameraPermission() - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.launchGalleryEvent - .collect { - galleryLauncher.launch(GALLERY_IMAGE_FILTER) - delay(timeMillis = 2000) - } - } - } - } - - override fun onResume() { - super.onResume() - checkPermissionGranted() - } - - override fun onDestroy() { - super.onDestroy() - cameraPermissionLauncher.unregister() - cameraExecutor.shutdown() - } - - @Composable - override fun ScreenContent(modifier: Modifier) { - SystemBarsIconsDisposable(darkIcons = false) - - QrScanningContent( - executor = { cameraExecutor }, - analyzer = { cameraAnalyzer }, - uiState = viewModel.uiState.collectAsStateWithLifecycle().value, - ) - } - - /** - * Method for requesting permission if there isn't one. - */ - private fun requestCameraPermission() { - if ( - ContextCompat.checkSelfPermission( - requireContext(), - Manifest.permission.CAMERA, - ) == PackageManager.PERMISSION_DENIED - ) { - cameraPermissionLauncher.launch(Manifest.permission.CAMERA) - } - } - - /** - * Method for checking if permission was granted after user opened Settings screen. - * If permission was granted dismiss bottom sheet. - */ - private fun checkPermissionGranted() { - if (ContextCompat.checkSelfPermission( - requireContext(), - Manifest.permission.CAMERA, - ) == PackageManager.PERMISSION_GRANTED - ) { - viewModel.onDismissBottomSheetState() - } - } - - private fun String?.isImageMimeType() = this?.startsWith(prefix = "$IMAGE_MIME_TYPE/") == true - - companion object { - - private const val IMAGE_MIME_TYPE = "image" - private const val GALLERY_IMAGE_FILTER = "$IMAGE_MIME_TYPE/*" - - fun create() = QrScanningFragment() - } -} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/di/QrScanningFeatureModule.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/di/QrScanningFeatureModule.kt new file mode 100644 index 0000000000..b623deb575 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/di/QrScanningFeatureModule.kt @@ -0,0 +1,25 @@ +package com.tangem.feature.qrscanning.di + +import com.tangem.core.decompose.model.Model +import com.tangem.feature.qrscanning.DefaultQrScanningComponent +import com.tangem.feature.qrscanning.QrScanningComponent +import com.tangem.feature.qrscanning.model.QrScanningModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface QrScanningFeatureModule { + + @Binds + fun bindComponentFactory(impl: DefaultQrScanningComponent.Factory): QrScanningComponent.Factory + + @Binds + @IntoMap + @ClassKey(QrScanningModel::class) + fun bindModel(model: QrScanningModel): Model +} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/di/QrScanningRouterModule.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/di/QrScanningRouterModule.kt deleted file mode 100644 index b22a6c713f..0000000000 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/di/QrScanningRouterModule.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.feature.qrscanning.di - -import com.tangem.common.routing.AppRouter -import com.tangem.feature.qrscanning.QrScanningRouter -import com.tangem.feature.qrscanning.navigation.DefaultQrScanningRouter -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityComponent -import dagger.hilt.android.scopes.ActivityScoped - -@Module -@InstallIn(ActivityComponent::class) -internal object QrScanningRouterModule { - - @Provides - @ActivityScoped - fun provideQrScanRouter(appRouter: AppRouter): QrScanningRouter { - return DefaultQrScanningRouter(appRouter) - } -} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/model/QrScanningClickIntents.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/model/QrScanningClickIntents.kt new file mode 100644 index 0000000000..b0702b0d62 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/model/QrScanningClickIntents.kt @@ -0,0 +1,16 @@ +package com.tangem.feature.qrscanning.model + +import kotlinx.coroutines.flow.SharedFlow + +internal interface QrScanningClickIntents { + + val launchGallery: SharedFlow + + fun onBackClick() + + fun onQrScanned(qrCode: String) + + fun onGalleryClicked() + + fun onSettingsClick() +} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/model/QrScanningModel.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/model/QrScanningModel.kt new file mode 100644 index 0000000000..ce3750e0c0 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/model/QrScanningModel.kt @@ -0,0 +1,91 @@ +package com.tangem.feature.qrscanning.model + +import androidx.compose.runtime.Stable +import com.tangem.common.routing.AppRouter +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.navigation.settings.SettingsManager +import com.tangem.data.card.sdk.CardSdkProvider +import com.tangem.domain.qrscanning.usecases.EmitQrScannedEventUseCase +import com.tangem.feature.qrscanning.QrScanningComponent +import com.tangem.feature.qrscanning.presentation.QrScanningState +import com.tangem.feature.qrscanning.presentation.QrScanningStateController +import com.tangem.feature.qrscanning.presentation.transformers.DismissBottomSheetTransformer +import com.tangem.feature.qrscanning.presentation.transformers.InitializeQrScanningStateTransformer +import com.tangem.feature.qrscanning.presentation.transformers.ShowCameraDeniedBottomSheetTransformer +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@Suppress("LongParameterList") +@Stable +@ModelScoped +internal class QrScanningModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + paramsContainer: ParamsContainer, + private val stateHolder: QrScanningStateController, + private val cardSdkProvider: CardSdkProvider, + private val emitQrScannedEventUseCase: EmitQrScannedEventUseCase, + private val settingsManager: SettingsManager, + private val appRouter: AppRouter, +) : Model(), QrScanningClickIntents { + + private val params = paramsContainer.require() + val uiState: StateFlow = stateHolder.uiState + private var isScanned = false + + override val launchGallery = MutableSharedFlow( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_LATEST, + ) + + init { + // samsung for some reason disables reader mode, and then it works unstable + // to prevent this disable ir manually before scan QR + cardSdkProvider.sdk.forceDisableReaderMode() + stateHolder.update(InitializeQrScanningStateTransformer(this, params.source, params.networkName)) + } + + fun onCameraDeniedState() { + stateHolder.update(ShowCameraDeniedBottomSheetTransformer(this)) + } + + fun onDismissBottomSheetState() { + stateHolder.update(DismissBottomSheetTransformer()) + } + + override fun onBackClick() = appRouter.pop() + + override fun onQrScanned(qrCode: String) { + if (qrCode.isNotBlank()) { + modelScope.launch(dispatchers.mainImmediate) { + emitQrScannedEventUseCase.invoke(params.source, qrCode) + } + if (!isScanned) { + appRouter.pop() + isScanned = true + } + } + } + + override fun onGalleryClicked() { + launchGallery.tryEmit(Unit) + if (stateHolder.value.bottomSheetConfig != null) { + stateHolder.update(DismissBottomSheetTransformer()) + } + } + + override fun onSettingsClick() { + settingsManager.openAppSettings() + } + + override fun onDestroy() { + super.onDestroy() + // don't forget enable reader mode after scan complete + cardSdkProvider.sdk.forceEnableReaderMode() + } +} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/navigation/DefaultQrScanningRouter.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/navigation/DefaultQrScanningRouter.kt deleted file mode 100644 index 3813274b68..0000000000 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/navigation/DefaultQrScanningRouter.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.feature.qrscanning.navigation - -import androidx.fragment.app.Fragment -import com.tangem.common.routing.AppRouter - -import com.tangem.feature.qrscanning.QrScanningFragment - -class DefaultQrScanningRouter( - private val router: AppRouter, -) : QrScanningInnerRouter { - override fun getEntryFragment(): Fragment = QrScanningFragment.create() - - override fun popBackStack() { - router.pop() - } -} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/navigation/QrScanningInnerRouter.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/navigation/QrScanningInnerRouter.kt deleted file mode 100644 index cdfb4cf247..0000000000 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/navigation/QrScanningInnerRouter.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.feature.qrscanning.navigation - -import com.tangem.feature.qrscanning.QrScanningRouter - -interface QrScanningInnerRouter : QrScanningRouter { - - fun popBackStack() -} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/InitializeQrScanningStateTransformer.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/InitializeQrScanningStateTransformer.kt index 2f38d3e545..7c6d08b333 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/InitializeQrScanningStateTransformer.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/InitializeQrScanningStateTransformer.kt @@ -5,7 +5,7 @@ import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.qrscanning.models.SourceType import com.tangem.feature.qrscanning.impl.R import com.tangem.feature.qrscanning.presentation.QrScanningState -import com.tangem.feature.qrscanning.viewmodel.QrScanningClickIntents +import com.tangem.feature.qrscanning.model.QrScanningClickIntents internal class InitializeQrScanningStateTransformer( private val clickIntents: QrScanningClickIntents, diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/ShowCameraDeniedBottomSheetTransformer.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/ShowCameraDeniedBottomSheetTransformer.kt index 5dedeffebc..dfd650019f 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/ShowCameraDeniedBottomSheetTransformer.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/ShowCameraDeniedBottomSheetTransformer.kt @@ -3,7 +3,7 @@ package com.tangem.feature.qrscanning.presentation.transformers import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.feature.qrscanning.presentation.CameraDeniedBottomSheetConfig import com.tangem.feature.qrscanning.presentation.QrScanningState -import com.tangem.feature.qrscanning.viewmodel.QrScanningClickIntents +import com.tangem.feature.qrscanning.model.QrScanningClickIntents internal class ShowCameraDeniedBottomSheetTransformer( private val clickIntents: QrScanningClickIntents, diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt deleted file mode 100644 index 2ff968422a..0000000000 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.feature.qrscanning.viewmodel - -import com.tangem.domain.qrscanning.models.SourceType -import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter -import kotlinx.coroutines.CoroutineScope -import kotlin.properties.Delegates - -internal open class BaseQrScanningClickIntents { - - protected val router: QrScanningInnerRouter get() = _router - protected val viewModelScope: CoroutineScope get() = _viewModelScope - protected val source: SourceType get() = _source - - private var _router: QrScanningInnerRouter by Delegates.notNull() - private var _viewModelScope: CoroutineScope by Delegates.notNull() - private var _source: SourceType by Delegates.notNull() - - open fun initialize(router: QrScanningInnerRouter, source: SourceType, coroutineScope: CoroutineScope) { - _router = router - _viewModelScope = coroutineScope - _source = source - } -} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt deleted file mode 100644 index adfaf8bcff..0000000000 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.tangem.feature.qrscanning.viewmodel - -import com.tangem.core.navigation.settings.SettingsManager -import com.tangem.domain.qrscanning.usecases.EmitQrScannedEventUseCase -import com.tangem.feature.qrscanning.presentation.QrScanningStateController -import com.tangem.feature.qrscanning.presentation.transformers.DismissBottomSheetTransformer -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.hilt.android.scopes.ViewModelScoped -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.launch -import javax.inject.Inject - -internal interface QrScanningClickIntents { - - val launchGallery: SharedFlow - - fun onBackClick() - - fun onQrScanned(qrCode: String) - - fun onGalleryClicked() - - fun onSettingsClick() -} - -@ViewModelScoped -internal class QrScanningClickIntentsImplementor @Inject constructor( - private val stateHolder: QrScanningStateController, - private val emitQrScannedEventUseCase: EmitQrScannedEventUseCase, - private val settingsManager: SettingsManager, - private val dispatcher: CoroutineDispatcherProvider, -) : BaseQrScanningClickIntents(), QrScanningClickIntents { - - private var isScanned = false - - override val launchGallery = MutableSharedFlow( - extraBufferCapacity = 1, - onBufferOverflow = BufferOverflow.DROP_LATEST, - ) - - override fun onBackClick() = router.popBackStack() - - override fun onQrScanned(qrCode: String) { - if (qrCode.isNotBlank()) { - viewModelScope.launch(dispatcher.mainImmediate) { - emitQrScannedEventUseCase.invoke(source, qrCode) - } - if (!isScanned) { - router.popBackStack() - isScanned = true - } - } - } - - override fun onGalleryClicked() { - launchGallery.tryEmit(Unit) - if (stateHolder.value.bottomSheetConfig != null) { - stateHolder.update(DismissBottomSheetTransformer()) - } - } - - override fun onSettingsClick() { - settingsManager.openAppSettings() - } -} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt deleted file mode 100644 index ceff5566d6..0000000000 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.tangem.feature.qrscanning.viewmodel - -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.tangem.common.routing.AppRoute -import com.tangem.data.card.sdk.CardSdkProvider -import com.tangem.domain.qrscanning.models.SourceType -import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter -import com.tangem.feature.qrscanning.presentation.QrScanningState -import com.tangem.feature.qrscanning.presentation.QrScanningStateController -import com.tangem.feature.qrscanning.presentation.transformers.DismissBottomSheetTransformer -import com.tangem.feature.qrscanning.presentation.transformers.InitializeQrScanningStateTransformer -import com.tangem.feature.qrscanning.presentation.transformers.ShowCameraDeniedBottomSheetTransformer -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.StateFlow -import javax.inject.Inject - -@HiltViewModel -internal class QrScanningViewModel @Inject constructor( - private val stateHolder: QrScanningStateController, - private val clickIntents: QrScanningClickIntentsImplementor, - private val cardSdkProvider: CardSdkProvider, - savedStateHandle: SavedStateHandle, -) : ViewModel() { - - private val source: SourceType = savedStateHandle.get(AppRoute.QrScanning.SOURCE_KEY) - ?.let { SourceType.entries[it] } - ?: error("Source is mandatory") - private val network: String? = savedStateHandle[AppRoute.QrScanning.NETWORK_KEY] - - val uiState: StateFlow = stateHolder.uiState - val launchGalleryEvent: SharedFlow = clickIntents.launchGallery - - init { - // samsung for some reason disables reader mode, and then it works unstable - // to prevent this disable ir manually before scan QR - cardSdkProvider.sdk.forceDisableReaderMode() - } - - fun setRouter(router: QrScanningInnerRouter) { - clickIntents.initialize( - router = router, - source = source, - coroutineScope = viewModelScope, - ) - stateHolder.update(InitializeQrScanningStateTransformer(clickIntents, source, network)) - } - - fun onQrScanned(qrCode: String) = clickIntents.onQrScanned(qrCode) - - fun onCameraDeniedState() { - stateHolder.update(ShowCameraDeniedBottomSheetTransformer(clickIntents)) - } - - fun onDismissBottomSheetState() { - stateHolder.update(DismissBottomSheetTransformer()) - } - - override fun onCleared() { - super.onCleared() - // don't forget enable reader mode after scan complete - cardSdkProvider.sdk.forceEnableReaderMode() - } -} \ No newline at end of file diff --git a/features/referral/api/.gitignore b/features/referral/api/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/features/referral/api/.gitignore @@ -0,0 +1 @@ +/build diff --git a/features/referral/api/build.gradle.kts b/features/referral/api/build.gradle.kts new file mode 100644 index 0000000000..22a3ba6fa3 --- /dev/null +++ b/features/referral/api/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.serialization) + id("kotlin-parcelize") + id("configuration") +} + +android { + namespace = "com.tangem.features.referral.api" +} + +dependencies { + /* Project - Core */ + implementation(projects.core.decompose) + implementation(projects.core.ui) + + /* Project - Domain */ + implementation(projects.domain.wallets.models) +} \ No newline at end of file diff --git a/features/referral/api/src/main/kotlin/com/tangem/feature/referral/api/ReferralComponent.kt b/features/referral/api/src/main/kotlin/com/tangem/feature/referral/api/ReferralComponent.kt new file mode 100644 index 0000000000..de0cda3a93 --- /dev/null +++ b/features/referral/api/src/main/kotlin/com/tangem/feature/referral/api/ReferralComponent.kt @@ -0,0 +1,14 @@ +package com.tangem.feature.referral.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.wallets.models.UserWalletId + +interface ReferralComponent : ComposableContentComponent { + + data class Params( + val userWalletId: UserWalletId, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/referral/domain/build.gradle.kts b/features/referral/domain/build.gradle.kts index a77d439bbb..0ca36ddd1a 100644 --- a/features/referral/domain/build.gradle.kts +++ b/features/referral/domain/build.gradle.kts @@ -13,6 +13,7 @@ dependencies { /** Libs */ implementation(projects.core.utils) + implementation(projects.core.decompose) /** Core modules */ implementation(projects.libs.crypto) diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt index 172c41b27d..4e5166e070 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt @@ -1,5 +1,7 @@ package com.tangem.feature.referral.domain.di +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.di.ModelComponent import com.tangem.domain.card.DerivePublicKeysUseCase import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase @@ -10,15 +12,13 @@ import com.tangem.lib.crypto.UserWalletManager import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.components.ViewModelComponent -import dagger.hilt.android.scopes.ViewModelScoped @Module -@InstallIn(ViewModelComponent::class) +@InstallIn(ModelComponent::class) class ReferralDomainModule { @Provides - @ViewModelScoped + @ModelScoped fun provideReferralInteractor( referralRepository: ReferralRepository, userWalletManager: UserWalletManager, diff --git a/features/referral/impl/.gitignore b/features/referral/impl/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/referral/impl/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/referral/presentation/build.gradle.kts b/features/referral/impl/build.gradle.kts similarity index 93% rename from features/referral/presentation/build.gradle.kts rename to features/referral/impl/build.gradle.kts index 6ee99e86cb..cf2272bee5 100644 --- a/features/referral/presentation/build.gradle.kts +++ b/features/referral/impl/build.gradle.kts @@ -11,6 +11,9 @@ android { } dependencies { + /** Api */ + api(projects.features.referral.api) + /** Core modules */ implementation(projects.core.analytics) implementation(projects.core.analytics.models) @@ -18,6 +21,7 @@ dependencies { implementation(projects.core.res) implementation(projects.core.utils) implementation(projects.core.ui) + implementation(projects.core.decompose) implementation(projects.libs.crypto) implementation(projects.common.routing) diff --git a/features/referral/impl/src/main/java/com/tangem/feature/referral/DefaultReferralComponent.kt b/features/referral/impl/src/main/java/com/tangem/feature/referral/DefaultReferralComponent.kt new file mode 100644 index 0000000000..f30cefaab8 --- /dev/null +++ b/features/referral/impl/src/main/java/com/tangem/feature/referral/DefaultReferralComponent.kt @@ -0,0 +1,30 @@ +package com.tangem.feature.referral + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.feature.referral.api.ReferralComponent +import com.tangem.feature.referral.model.ReferralModel +import com.tangem.feature.referral.ui.ReferralScreen +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +class DefaultReferralComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: ReferralComponent.Params, +) : ReferralComponent, AppComponentContext by appComponentContext { + + private val model: ReferralModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + ReferralScreen(stateHolder = model.uiState) + } + + @AssistedFactory + interface Factory : ReferralComponent.Factory { + override fun create(context: AppComponentContext, params: ReferralComponent.Params): DefaultReferralComponent + } +} \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/analytics/ReferralEvents.kt b/features/referral/impl/src/main/java/com/tangem/feature/referral/analytics/ReferralEvents.kt similarity index 100% rename from features/referral/presentation/src/main/java/com/tangem/feature/referral/analytics/ReferralEvents.kt rename to features/referral/impl/src/main/java/com/tangem/feature/referral/analytics/ReferralEvents.kt diff --git a/features/referral/impl/src/main/java/com/tangem/feature/referral/di/ComponentModule.kt b/features/referral/impl/src/main/java/com/tangem/feature/referral/di/ComponentModule.kt new file mode 100644 index 0000000000..54355a0a6c --- /dev/null +++ b/features/referral/impl/src/main/java/com/tangem/feature/referral/di/ComponentModule.kt @@ -0,0 +1,25 @@ +package com.tangem.feature.referral.di + +import com.tangem.core.decompose.model.Model +import com.tangem.feature.referral.DefaultReferralComponent +import com.tangem.feature.referral.api.ReferralComponent +import com.tangem.feature.referral.model.ReferralModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface ComponentModule { + + @Binds + fun provideReferralComponentFactory(impl: DefaultReferralComponent.Factory): ReferralComponent.Factory + + @Binds + @IntoMap + @ClassKey(ReferralModel::class) + fun bindModel(model: ReferralModel): Model +} \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt b/features/referral/impl/src/main/java/com/tangem/feature/referral/model/ReferralModel.kt similarity index 78% rename from features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt rename to features/referral/impl/src/main/java/com/tangem/feature/referral/model/ReferralModel.kt index 57d35abc7d..a2379d3f8a 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt +++ b/features/referral/impl/src/main/java/com/tangem/feature/referral/model/ReferralModel.kt @@ -1,21 +1,20 @@ -package com.tangem.feature.referral.viewmodels +package com.tangem.feature.referral.model -import android.os.Bundle +import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.bundle.unbundle +import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.url.UrlOpener import com.tangem.domain.demo.IsDemoCardUseCase -import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.feature.referral.analytics.ReferralEvents +import com.tangem.feature.referral.api.ReferralComponent import com.tangem.feature.referral.domain.ReferralInteractor import com.tangem.feature.referral.domain.errors.ReferralError import com.tangem.feature.referral.domain.models.DiscountType @@ -25,29 +24,26 @@ import com.tangem.feature.referral.models.DemoModeException import com.tangem.feature.referral.models.ReferralStateHolder import com.tangem.feature.referral.models.ReferralStateHolder.ErrorSnackbar import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoState -import com.tangem.feature.referral.router.ReferralRouter -import dagger.hilt.android.lifecycle.HiltViewModel +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.launch import javax.inject.Inject -import kotlin.properties.Delegates @Suppress("LongParameterList") -@HiltViewModel -internal class ReferralViewModel @Inject constructor( +@Stable +@ModelScoped +internal class ReferralModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, private val referralInteractor: ReferralInteractor, private val analyticsEventHandler: AnalyticsEventHandler, private val shareManager: ShareManager, private val urlOpener: UrlOpener, private val getUserWalletUseCase: GetUserWalletUseCase, private val isDemoCardUseCase: IsDemoCardUseCase, - savedStateHandle: SavedStateHandle, -) : ViewModel() { + private val appRouter: AppRouter, +) : Model() { - private val userWalletId = savedStateHandle.get(AppRoute.ReferralProgram.USER_WALLET_ID_KEY) - ?.unbundle(UserWalletId.serializer()) - ?: error("User wallet ID is required for Referral screen") - - private var referralRouter: ReferralRouter by Delegates.notNull() + private val params = paramsContainer.require() private var lastReferralData: ReferralData? = null @@ -55,20 +51,14 @@ internal class ReferralViewModel @Inject constructor( private set init { + analyticsEventHandler.send(ReferralEvents.ReferralScreenOpened) loadReferralData() } - fun setRouter(router: ReferralRouter) { - referralRouter = router - uiState = uiState.copy(headerState = ReferralStateHolder.HeaderState(onBackClicked = router::back)) - } - - fun onScreenOpened() { - analyticsEventHandler.send(ReferralEvents.ReferralScreenOpened) - } - private fun createInitiallyUiState() = ReferralStateHolder( - headerState = ReferralStateHolder.HeaderState(onBackClicked = { }), + headerState = ReferralStateHolder.HeaderState( + onBackClicked = appRouter::pop, + ), referralInfoState = ReferralInfoState.Loading, errorSnackbar = null, analytics = ReferralStateHolder.Analytics( @@ -80,9 +70,9 @@ internal class ReferralViewModel @Inject constructor( private fun loadReferralData() { uiState = uiState.copy(referralInfoState = ReferralInfoState.Loading) - viewModelScope.launch { + modelScope.launch { runCatching { - referralInteractor.getReferralStatus(userWalletId).apply { + referralInteractor.getReferralStatus(params.userWalletId).apply { lastReferralData = this } } @@ -92,15 +82,15 @@ internal class ReferralViewModel @Inject constructor( } private fun participate() { - val userWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: error("User wallet not found") + val userWallet = getUserWalletUseCase(params.userWalletId).getOrNull() ?: error("User wallet not found") if (isDemoCardUseCase(cardId = userWallet.cardId)) { showErrorSnackbar(DemoModeException()) } else { analyticsEventHandler.send(ReferralEvents.ClickParticipate) uiState = uiState.copy(referralInfoState = ReferralInfoState.Loading) - viewModelScope.launch { - runCatching { referralInteractor.startReferral(userWalletId) } + modelScope.launch { + runCatching { referralInteractor.startReferral(params.userWalletId) } .onSuccess(::showContent) .onFailure { throwable -> if (throwable is ReferralError.UserCancelledException) { @@ -121,7 +111,7 @@ internal class ReferralViewModel @Inject constructor( private fun showErrorSnackbar(throwable: Throwable) { uiState = uiState.copy( - errorSnackbar = ErrorSnackbar(throwable = throwable, onOkClicked = referralRouter::back), + errorSnackbar = ErrorSnackbar(throwable = throwable, onOkClicked = appRouter::pop), ) } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/DemoModeException.kt b/features/referral/impl/src/main/java/com/tangem/feature/referral/models/DemoModeException.kt similarity index 100% rename from features/referral/presentation/src/main/java/com/tangem/feature/referral/models/DemoModeException.kt rename to features/referral/impl/src/main/java/com/tangem/feature/referral/models/DemoModeException.kt diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt b/features/referral/impl/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt similarity index 100% rename from features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt rename to features/referral/impl/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt b/features/referral/impl/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt similarity index 100% rename from features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt rename to features/referral/impl/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt b/features/referral/impl/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt similarity index 100% rename from features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt rename to features/referral/impl/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/impl/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt similarity index 100% rename from features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt rename to features/referral/impl/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/impl/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt similarity index 100% rename from features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt rename to features/referral/impl/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt deleted file mode 100644 index cec296cc80..0000000000 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.tangem.feature.referral - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.fragment.app.viewModels -import com.tangem.common.routing.AppRouter -import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.feature.referral.router.ReferralRouter -import com.tangem.feature.referral.ui.ReferralScreen -import com.tangem.feature.referral.viewmodels.ReferralViewModel -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -@AndroidEntryPoint -class ReferralFragment : ComposeFragment() { - - @Inject - override lateinit var uiDependencies: UiDependencies - - @Inject - internal lateinit var appRouter: AppRouter - - private val viewModel by viewModels() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - viewModel.onScreenOpened() - viewModel.setRouter(ReferralRouter(appRouter)) - } - - @Composable - override fun ScreenContent(modifier: Modifier) { - ReferralScreen(stateHolder = viewModel.uiState) - } -} \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/router/ReferralRouter.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/router/ReferralRouter.kt deleted file mode 100644 index a8ac11e54b..0000000000 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/router/ReferralRouter.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.feature.referral.router - -import com.tangem.common.routing.AppRouter - -internal class ReferralRouter( - private val appRouter: AppRouter, -) { - - fun back() { - appRouter.pop() - } -} \ No newline at end of file diff --git a/features/send/api/build.gradle.kts b/features/send/api/build.gradle.kts index eb561fcf20..b5867a273b 100644 --- a/features/send/api/build.gradle.kts +++ b/features/send/api/build.gradle.kts @@ -10,6 +10,13 @@ android { } dependencies { + /** Core */ + implementation(projects.core.decompose) + implementation(projects.core.ui) + + /** Domain models */ + implementation(projects.domain.wallets.models) + implementation(projects.domain.tokens.models) /** AndroidX */ implementation(deps.androidx.fragment.ktx) diff --git a/features/send/api/src/main/kotlin/com/tangem/features/send/api/SendComponent.kt b/features/send/api/src/main/kotlin/com/tangem/features/send/api/SendComponent.kt new file mode 100644 index 0000000000..987d2e79dc --- /dev/null +++ b/features/send/api/src/main/kotlin/com/tangem/features/send/api/SendComponent.kt @@ -0,0 +1,20 @@ +package com.tangem.features.send.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId + +interface SendComponent : ComposableContentComponent { + + data class Params( + val userWalletId: UserWalletId, + val currency: CryptoCurrency, + val transactionId: String? = null, + val amount: String? = null, + val tag: String? = null, + val destinationAddress: String? = null, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/send/api/src/main/kotlin/com/tangem/features/send/api/navigation/SendRouter.kt b/features/send/api/src/main/kotlin/com/tangem/features/send/api/navigation/SendRouter.kt deleted file mode 100644 index 286f908ad8..0000000000 --- a/features/send/api/src/main/kotlin/com/tangem/features/send/api/navigation/SendRouter.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.features.send.api.navigation - -import androidx.fragment.app.Fragment - -interface SendRouter { - - fun getEntryFragment(): Fragment -} \ No newline at end of file diff --git a/features/send/impl/build.gradle.kts b/features/send/impl/build.gradle.kts index 6ab25de93a..8883699dbb 100644 --- a/features/send/impl/build.gradle.kts +++ b/features/send/impl/build.gradle.kts @@ -50,6 +50,7 @@ dependencies { implementation(projects.core.analytics) implementation(projects.core.analytics.models) implementation(projects.core.datasource) + implementation(projects.core.decompose) /** Common */ implementation(projects.common.ui) @@ -83,6 +84,7 @@ dependencies { /** Feature modules */ implementation(projects.features.send.api) implementation(projects.features.tokendetails.api) + implementation(projects.features.txhistory.api) implementation(projects.features.qrScanning.api) /** DI */ diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/DefaultSendComponent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/DefaultSendComponent.kt new file mode 100644 index 0000000000..e08ff5f208 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/DefaultSendComponent.kt @@ -0,0 +1,35 @@ +package com.tangem.features.send.impl + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.features.send.api.SendComponent +import com.tangem.features.send.impl.presentation.model.SendModel +import com.tangem.features.send.impl.presentation.ui.SendScreen +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultSendComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: SendComponent.Params, +) : SendComponent, AppComponentContext by appComponentContext { + + private val model: SendModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val currentState = model.stateRouter.currentState.collectAsStateWithLifecycle() + val uiState by model.uiState.collectAsStateWithLifecycle() + + SendScreen(uiState, currentState.value) + } + + @AssistedFactory + interface Factory : SendComponent.Factory { + override fun create(context: AppComponentContext, params: SendComponent.Params): DefaultSendComponent + } +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendModule.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendModule.kt new file mode 100644 index 0000000000..86c1ce5b9f --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendModule.kt @@ -0,0 +1,38 @@ +package com.tangem.features.send.impl.di + +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.send.api.SendComponent +import com.tangem.features.send.impl.DefaultSendComponent +import com.tangem.features.send.impl.navigation.DefaultSendRouter +import com.tangem.features.send.impl.navigation.InnerSendRouter +import com.tangem.features.send.impl.presentation.model.SendModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface SendModule { + + @Binds + fun bindComponentFactory(factory: DefaultSendComponent.Factory): SendComponent.Factory + + @Binds + @IntoMap + @ClassKey(SendModel::class) + fun bindModel(model: SendModel): Model +} + +@Module +@InstallIn(ModelComponent::class) +internal interface SendModelModule { + + @Binds + @ModelScoped + fun bindRouter(router: DefaultSendRouter): InnerSendRouter +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendRouterModule.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendRouterModule.kt deleted file mode 100644 index b99742c742..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendRouterModule.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.features.send.impl.di - -import com.tangem.common.routing.AppRouter -import com.tangem.core.navigation.url.UrlOpener -import com.tangem.features.send.api.navigation.SendRouter -import com.tangem.features.send.impl.navigation.DefaultSendRouter -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityComponent -import dagger.hilt.android.scopes.ActivityScoped - -/** - * DI module provides implementation of [SendRouter] - */ -@Module -@InstallIn(ActivityComponent::class) -internal object SendRouterModule { - - @Provides - @ActivityScoped - fun provideSendRouter(appRouter: AppRouter, urlOpener: UrlOpener): SendRouter { - return DefaultSendRouter(appRouter, urlOpener) - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt index ed9f1f7b14..788ea93ba2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt @@ -1,21 +1,20 @@ package com.tangem.features.send.impl.navigation -import androidx.fragment.app.Fragment import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.navigation.url.UrlOpener import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.features.send.impl.presentation.SendFragment +import javax.inject.Inject -internal class DefaultSendRouter( +@ModelScoped +internal class DefaultSendRouter @Inject constructor( private val router: AppRouter, private val urlOpener: UrlOpener, ) : InnerSendRouter { - override fun getEntryFragment(): Fragment = SendFragment.create() - override fun openUrl(url: String) { urlOpener.openUrl(url) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/InnerSendRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/InnerSendRouter.kt index f061eaba01..9774e13bfe 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/InnerSendRouter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/InnerSendRouter.kt @@ -2,9 +2,8 @@ package com.tangem.features.send.impl.navigation import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.features.send.api.navigation.SendRouter -interface InnerSendRouter : SendRouter { +interface InnerSendRouter { /** Open website by [url] */ fun openUrl(url: String) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt deleted file mode 100644 index 48899c5eb3..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.tangem.features.send.impl.presentation - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.fragment.app.viewModels -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.AppRouter -import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.features.send.api.navigation.SendRouter -import com.tangem.features.send.impl.navigation.InnerSendRouter -import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.features.send.impl.presentation.ui.SendScreen -import com.tangem.features.send.impl.presentation.viewmodel.SendViewModel -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -/** - * Send fragment - */ -@AndroidEntryPoint -internal class SendFragment : ComposeFragment() { - - @Inject - override lateinit var uiDependencies: UiDependencies - - @Inject - lateinit var router: SendRouter - - @Inject - lateinit var appRouter: AppRouter - - @Inject - lateinit var analyticsEventsHandler: AnalyticsEventHandler - - private val viewModel by viewModels() - private val innerSendRouter: InnerSendRouter - get() = requireNotNull(router as? InnerSendRouter) { - "innerSendRouter should be instance of InnerSendRouter" - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - lifecycle.addObserver(viewModel) - - val isEditingDisabled = arguments?.getString(AppRoute.Send.TRANSACTION_ID_KEY) != null - viewModel.setRouter( - innerSendRouter, - StateRouter( - appRouter = appRouter, - isEditingDisabled = isEditingDisabled, - analyticsEventsHandler = analyticsEventsHandler, - ), - ) - } - - @Composable - override fun ScreenContent(modifier: Modifier) { - val currentState = viewModel.stateRouter.currentState.collectAsStateWithLifecycle() - val uiState by viewModel.uiState.collectAsStateWithLifecycle() - - SendScreen(uiState, currentState.value) - } - - override fun onDestroy() { - lifecycle.removeObserver(viewModel) - super.onDestroy() - } - - companion object { - /** Create send fragment instance */ - fun create(): SendFragment = SendFragment() - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendClickIntents.kt similarity index 96% rename from features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt rename to features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendClickIntents.kt index e77536f76c..8ec20b06ce 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendClickIntents.kt @@ -1,4 +1,4 @@ -package com.tangem.features.send.impl.presentation.viewmodel +package com.tangem.features.send.impl.presentation.model import com.tangem.common.ui.amountScreen.AmountScreenClickIntents import com.tangem.common.ui.notifications.NotificationUM diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendModel.kt similarity index 94% rename from features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt rename to features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendModel.kt index 945c953dba..55e0edc294 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendModel.kt @@ -1,20 +1,21 @@ -package com.tangem.features.send.impl.presentation.viewmodel +package com.tangem.features.send.impl.presentation.model -import android.os.Bundle import android.os.SystemClock -import androidx.lifecycle.* +import androidx.compose.runtime.Stable import arrow.core.Either import arrow.core.getOrElse import arrow.core.left import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.bundle.unbundle +import com.tangem.common.routing.AppRouter import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.share.ShareManager import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase @@ -47,6 +48,7 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.GetWalletsUseCase +import com.tangem.features.send.api.SendComponent import com.tangem.features.send.impl.navigation.InnerSendRouter import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents @@ -58,12 +60,13 @@ import com.tangem.features.send.impl.presentation.state.amount.AmountStateFactor import com.tangem.features.send.impl.presentation.state.confirm.SendNotificationFactory import com.tangem.features.send.impl.presentation.state.fee.* import com.tangem.features.send.impl.presentation.state.recipient.RecipientSendFactory +import com.tangem.features.txhistory.TxHistoryFeatureToggles +import com.tangem.features.txhistory.entity.TxHistoryContentUpdateEmitter import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.Provider import com.tangem.utils.coroutines.* import com.tangem.utils.extensions.orZero import com.tangem.utils.extensions.stripZeroPlainString -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import timber.log.Timber @@ -73,9 +76,10 @@ import javax.inject.Inject import kotlin.properties.Delegates @Suppress("LongParameterList", "TooManyFunctions", "LargeClass") -@HiltViewModel -internal class SendViewModel @Inject constructor( - private val dispatchers: CoroutineDispatcherProvider, +@Stable +@ModelScoped +internal class SendModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, private val getUserWalletUseCase: GetUserWalletUseCase, private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, @@ -108,32 +112,34 @@ internal class SendViewModel @Inject constructor( private val getCardInfoUseCase: GetCardInfoUseCase, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, private val shareManager: ShareManager, + private val txHistoryFeatureToggles: TxHistoryFeatureToggles, + private val txHistoryContentUpdateEmitter: TxHistoryContentUpdateEmitter, @DelayedWork private val coroutineScope: CoroutineScope, + private val innerRouter: InnerSendRouter, + private val appRouter: AppRouter, + paramsContainer: ParamsContainer, validateTransactionUseCase: ValidateTransactionUseCase, getCurrencyCheckUseCase: GetCurrencyCheckUseCase, isFeeApproximateUseCase: IsFeeApproximateUseCase, getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase, - savedStateHandle: SavedStateHandle, -) : ViewModel(), DefaultLifecycleObserver, SendClickIntents { +) : Model(), SendClickIntents { - private val userWalletId: UserWalletId = savedStateHandle.get(AppRoute.Send.USER_WALLET_ID_KEY) - ?.unbundle(UserWalletId.serializer()) - ?: error("This screen can't open without `UserWalletId`") + private val params = paramsContainer.require() - private val cryptoCurrency: CryptoCurrency = savedStateHandle.get(AppRoute.Send.CRYPTO_CURRENCY_KEY) - ?.unbundle(CryptoCurrency.serializer()) - ?: error("This screen can't open without `CryptoCurrency`") - - private val transactionId: String? = savedStateHandle[AppRoute.Send.TRANSACTION_ID_KEY] - private val amount: String? = savedStateHandle[AppRoute.Send.AMOUNT_KEY] - private val destinationAddress: String? = savedStateHandle[AppRoute.Send.DESTINATION_ADDRESS_KEY] - private val memo: String? = savedStateHandle[AppRoute.Send.TAG_KEY] + private val userWalletId: UserWalletId = params.userWalletId + private val cryptoCurrency: CryptoCurrency = params.currency + private val transactionId: String? = params.transactionId + private val amount: String? = params.amount + private val destinationAddress: String? = params.destinationAddress + private val memo: String? = params.tag private val selectedAppCurrencyFlow: StateFlow = createSelectedAppCurrencyFlow() - private var innerRouter: InnerSendRouter by Delegates.notNull() - var stateRouter: StateRouter by Delegates.notNull() - private set + val stateRouter = StateRouter( + appRouter = appRouter, + isEditingDisabled = transactionId != null, + analyticsEventsHandler = analyticsEventHandler, + ) private val stateFactory = SendStateFactory( clickIntents = this, @@ -234,33 +240,26 @@ internal class SendViewModel @Inject constructor( subscribeOnCurrencyStatusUpdates() subscribeOnBalanceHidden() getTapHelpPreviewAvailability() - } - override fun onCreate(owner: LifecycleOwner) { onStateActive() } - override fun onCleared() { - super.onCleared() + override fun onDestroy() { + super.onDestroy() balanceHidingJobHolder.cancel() balanceJobHolder.cancel() stateRouter.clear() } - fun setRouter(router: InnerSendRouter, stateRouter: StateRouter) { - innerRouter = router - this.stateRouter = stateRouter - } - private fun subscribeOnQRScannerResult() { listenToQrScanningUseCase(SourceType.SEND) .getOrElse { emptyFlow() } .onEach(::onQrCodeScanned) - .launchIn(viewModelScope) + .launchIn(modelScope) } private fun subscribeOnCurrencyStatusUpdates() { - viewModelScope.launch { + modelScope.launch { getUserWalletUseCase(userWalletId).fold( ifRight = { wallet -> userWallet = wallet @@ -289,7 +288,7 @@ internal class SendViewModel @Inject constructor( .onEach { uiState.value = stateFactory.getOnHideBalanceState(isBalanceHidden = it.isBalanceHidden) } - .launchIn(viewModelScope) + .launchIn(modelScope) .saveIn(balanceHidingJobHolder) } @@ -310,7 +309,7 @@ internal class SendViewModel @Inject constructor( } private fun getTapHelpPreviewAvailability() { - viewModelScope.launch { + modelScope.launch { isTapHelpPreviewEnabled = isSendTapHelpEnabledUseCase().getOrElse { false } } } @@ -362,7 +361,7 @@ internal class SendViewModel @Inject constructor( maybeAppCurrency.getOrElse { AppCurrency.Default } } .stateIn( - scope = viewModelScope, + scope = modelScope, started = SharingStarted.Eagerly, initialValue = AppCurrency.Default, ) @@ -395,13 +394,13 @@ internal class SendViewModel @Inject constructor( private fun getWalletsAndRecent() { getUserWallets() - viewModelScope.launch { + modelScope.launch { getTxHistory() } } private fun getUserWallets() { - viewModelScope.launch { + modelScope.launch { runCatching { waitForDelay(delay = RECENT_LOAD_DELAY) { getWalletsUseCase.invokeSync() @@ -466,7 +465,7 @@ internal class SendViewModel @Inject constructor( else -> Unit } } - .launchIn(viewModelScope) + .launchIn(modelScope) } private fun updateNotifications() { @@ -475,7 +474,7 @@ internal class SendViewModel @Inject constructor( .distinctUntilChanged() .onEach { uiState.value = stateFactory.getSendNotificationState(notifications = it) } .flowOn(dispatchers.main) - .launchIn(viewModelScope) + .launchIn(modelScope) .saveIn(sendNotificationsJobHolder) } @@ -485,7 +484,7 @@ internal class SendViewModel @Inject constructor( .distinctUntilChanged() .onEach { uiState.value = feeStateFactory.getFeeNotificationState(notifications = it) } .flowOn(dispatchers.main) - .launchIn(viewModelScope) + .launchIn(modelScope) .saveIn(feeNotificationsJobHolder) } @@ -573,7 +572,7 @@ internal class SendViewModel @Inject constructor( val cardInfo = getCardInfoUseCase(userWallet.scanResponse).getOrNull() ?: return - viewModelScope.launch { + modelScope.launch { sendFeedbackEmailUseCase(type = FeedbackEmailType.TransactionSendingProblem(cardInfo = cardInfo)) } } @@ -615,7 +614,7 @@ internal class SendViewModel @Inject constructor( } private fun cancelFeeRequest() { - viewModelScope.launch { + modelScope.launch { feeJobHolder.cancel() } } @@ -660,7 +659,7 @@ internal class SendViewModel @Inject constructor( // region recipient state clicks override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) { - viewModelScope.launch { + modelScope.launch { if (!checkIfXrpAddressValue(value)) { uiState.value = recipientStateFactory.onRecipientAddressValueChange(value, isValuePasted = type != null) uiState.value = recipientStateFactory.getOnRecipientAddressValidationStarted() @@ -680,7 +679,7 @@ internal class SendViewModel @Inject constructor( } override fun onRecipientMemoValueChange(value: String, isValuePasted: Boolean) { - viewModelScope.launch { + modelScope.launch { if (!checkIfXrpAddressValue(value)) { uiState.value = recipientStateFactory.getOnRecipientMemoValueChange(value, isValuePasted) uiState.value = recipientStateFactory.getOnRecipientAddressValidationStarted() @@ -758,7 +757,7 @@ internal class SendViewModel @Inject constructor( } private fun loadFee() { - viewModelScope.launch { + modelScope.launch { val isShowStatus = uiState.value.feeState?.fee == null if (isShowStatus) { uiState.value = feeStateFactory.onFeeOnLoadingState() @@ -911,7 +910,7 @@ internal class SendViewModel @Inject constructor( reduceAmountBy = uiState.value.sendState?.reduceAmountBy ?: BigDecimal.ZERO, ) - viewModelScope.launch { + modelScope.launch { createTransactionUseCase( amount = receivingAmount.convertToSdkAmount(cryptoCurrency), fee = fee, @@ -969,7 +968,7 @@ internal class SendViewModel @Inject constructor( val receivingUserWallet = userWallets.firstOrNull { it.address == destinationAddress } ?: return - viewModelScope.launch { + modelScope.launch { addCryptoCurrenciesUseCase( userWalletId = receivingUserWallet.userWalletId, cryptoCurrency = cryptoCurrency, @@ -1017,11 +1016,15 @@ internal class SendViewModel @Inject constructor( ) txHistoryItemsCountEither.onRight { - getTxHistoryItemsUseCase( - userWalletId = userWalletId, - currency = cryptoCurrency, - refresh = true, - ) + if (txHistoryFeatureToggles.isFeatureEnabled) { + txHistoryContentUpdateEmitter.triggerUpdate() + } else { + getTxHistoryItemsUseCase( + userWalletId = userWalletId, + currency = cryptoCurrency, + refresh = true, + ) + } } } @@ -1031,7 +1034,7 @@ internal class SendViewModel @Inject constructor( val noErrorNotifications = sendState.notifications.none { it is NotificationUM.Error } if (!isSuccess && noErrorNotifications) { - viewModelScope.launch { + modelScope.launch { val feeUpdatedState = callFeeUseCase()?.fold( ifRight = { uiState.value = stateFactory.getSendingStateUpdate(isSending = false) @@ -1065,7 +1068,7 @@ internal class SendViewModel @Inject constructor( } private fun setNeverToShowTapHelp() { - viewModelScope.launch { + modelScope.launch { neverShowTapHelpUseCase() } uiState.value = stateFactory.getHiddenTapHelpState() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt index 1258c831fb..9d95fd1d61 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt @@ -8,7 +8,7 @@ import com.tangem.domain.transaction.error.SendTransactionError import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeStateFactory import com.tangem.features.send.impl.presentation.state.fee.FeeType -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.utils.Provider import java.math.BigDecimal diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index 030e8e81c7..4502a628ec 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -18,7 +18,7 @@ import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter import com.tangem.features.send.impl.presentation.state.fee.checkFeeCoverage import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientStateConverter -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt index ab01823bca..fd0769ac99 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt @@ -10,7 +10,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import kotlinx.collections.immutable.ImmutableList import java.math.BigDecimal diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt index 6335c3b154..809f8ada16 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt @@ -12,7 +12,7 @@ internal class StateRouter( private val analyticsEventsHandler: AnalyticsEventHandler, private val isEditingDisabled: Boolean, ) { - private var mutableCurrentState: MutableStateFlow = MutableStateFlow(getInitState()) + private val mutableCurrentState: MutableStateFlow = MutableStateFlow(getInitState()) val currentState: StateFlow get() = mutableCurrentState diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index ea3dc027aa..87b25f2824 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -34,7 +34,7 @@ import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.SendUiStateType import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.state.fee.* -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.lib.crypto.BlockchainUtils import com.tangem.lib.crypto.BlockchainUtils.isTezos import com.tangem.utils.Provider diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt index 4c557fecbb..54424e3b07 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt @@ -9,7 +9,7 @@ import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.state.fee.custom.BitcoinCustomFeeConverter import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter import com.tangem.features.send.impl.presentation.state.fee.custom.KaspaCustomFeeConverter -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt index 96985ed40d..6eaa94816c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt @@ -4,7 +4,7 @@ import com.tangem.common.ui.notifications.NotificationsFactory.addFeeUnreachable import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.SendUiStateType import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt index 83e40651a2..0ff24473ac 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt @@ -14,7 +14,7 @@ import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt index 23571ac345..71f672a60a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt @@ -9,7 +9,7 @@ import com.tangem.features.send.impl.presentation.state.fee.custom.BitcoinCustom import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter import com.tangem.features.send.impl.presentation.state.fee.custom.KaspaCustomFeeConverter import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.ImmutableList diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt index 2b7aa838e5..a88f105554 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt @@ -15,7 +15,7 @@ import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.state.fee.checkExceedBalance import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.lib.crypto.BlockchainUtils.isUseBitcoinFeeConverter import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt index b65735aeb2..e8695d1e87 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt @@ -14,7 +14,7 @@ import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.state.fee.checkExceedBalance import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumEIPCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumEIPCustomFeeConverter.kt index 14a249de6c..f436dd3051 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumEIPCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumEIPCustomFeeConverter.kt @@ -18,7 +18,7 @@ import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCusto import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.GAS_DECIMALS import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.GIGA_DECIMALS import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumLegacyCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumLegacyCustomFeeConverter.kt index b4058dd76d..cf8e3881b7 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumLegacyCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumLegacyCustomFeeConverter.kt @@ -18,7 +18,7 @@ import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCusto import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.GAS_DECIMALS import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.GIGA_DECIMALS import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/KaspaCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/KaspaCustomFeeConverter.kt index 769bd3ee75..b361065b43 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/KaspaCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/KaspaCustomFeeConverter.kt @@ -13,7 +13,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt index ca5adcd00a..482afa6bda 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt @@ -4,7 +4,7 @@ import com.tangem.common.ui.notifications.NotificationUM import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource import com.tangem.features.send.impl.presentation.state.fee.FeeType -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import java.math.BigDecimal @Suppress("TooManyFunctions") diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt index 2365bc31e2..a14a303ab2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt @@ -6,7 +6,7 @@ import androidx.compose.ui.text.input.KeyboardType import com.tangem.core.ui.extensions.resourceReference import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.utils.converter.Converter internal class SendRecipientAddressFieldConverter( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt index 273b6e0b6f..ad0c05fda2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt @@ -9,7 +9,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.Network import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt index 9b3f770fcf..7829d19b85 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt @@ -3,7 +3,7 @@ package com.tangem.features.send.impl.presentation.state.recipient import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.recipient.utils.* -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt index dedf7fa608..cbb85ab5e8 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt @@ -20,7 +20,7 @@ import com.tangem.features.send.impl.presentation.state.fee.FeeType import com.tangem.features.send.impl.presentation.state.previewdata.FeeStatePreviewData import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub import com.tangem.features.send.impl.presentation.ui.common.notifications -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents private const val FEE_SELECTOR_KEY = "FEE_SELECTOR_KEY" private const val FEE_CUSTOM_KEY = "FEE_CUSTOM_KEY" diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt index 2c83d49d07..1e8293ea4b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt @@ -25,7 +25,7 @@ import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fee.FeeType import com.tangem.features.send.impl.presentation.state.previewdata.FeeStatePreviewData import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents @Suppress("LongMethod") @Composable diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt index aaa589bde5..4838a3a3e3 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt @@ -32,7 +32,7 @@ import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.features.send.impl.presentation.state.previewdata.RecipientStatePreviewData import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import kotlinx.collections.immutable.ImmutableList private const val ADDRESS_FIELD_KEY = "ADDRESS_FIELD_KEY" diff --git a/features/staking/impl/build.gradle.kts b/features/staking/impl/build.gradle.kts index bbfa41e6e2..6ffb00cc29 100644 --- a/features/staking/impl/build.gradle.kts +++ b/features/staking/impl/build.gradle.kts @@ -75,6 +75,7 @@ dependencies { /** Feature modules */ implementation(projects.features.staking.api) + implementation(projects.features.txhistory.api) /** DI */ implementation(deps.hilt.android) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt index fdc4685953..e5911ef720 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt @@ -113,7 +113,7 @@ internal class StakingAnalyticSender( val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data return when (value.actionType) { - StakingActionCommonType.Enter -> StakingActionType.STAKE + is StakingActionCommonType.Enter -> StakingActionType.STAKE is StakingActionCommonType.Exit -> StakingActionType.UNSTAKE is StakingActionCommonType.Pending -> confirmationState?.pendingAction?.type ?: StakingActionType.UNKNOWN } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/di/StakingModule.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/di/StakingModule.kt index c2fc6404b9..912662375d 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/di/StakingModule.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/di/StakingModule.kt @@ -1,7 +1,7 @@ package com.tangem.features.staking.impl.di -import com.tangem.core.decompose.di.ComponentScoped -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.staking.api.StakingComponent import com.tangem.features.staking.impl.DefaultStakingComponent @@ -29,10 +29,10 @@ internal interface StakingModule { } @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface StakingComponentModule { @Binds - @ComponentScoped + @ModelScoped fun bindRouter(impl: DefaultStakingRouter): InnerStakingRouter } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/navigation/DefaultStakingRouter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/navigation/DefaultStakingRouter.kt index 9a600a0a93..c149089cad 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/navigation/DefaultStakingRouter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/navigation/DefaultStakingRouter.kt @@ -2,13 +2,13 @@ package com.tangem.features.staking.impl.navigation import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.navigation.url.UrlOpener import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId import javax.inject.Inject -@ComponentScoped +@ModelScoped internal class DefaultStakingRouter @Inject constructor( private val urlOpener: UrlOpener, private val router: AppRouter, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt index 3a4f7fe8ba..ebe6d7c95a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt @@ -12,7 +12,7 @@ import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomS import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.ParamsInterceptorHolder -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.share.ShareManager @@ -33,7 +33,6 @@ import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.stakekit.* import com.tangem.domain.staking.model.stakekit.action.StakingAction import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType -import com.tangem.domain.staking.model.stakekit.action.StakingActionType import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction import com.tangem.domain.tokens.* import com.tangem.domain.tokens.model.CryptoCurrency @@ -42,8 +41,6 @@ import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase import com.tangem.domain.transaction.usecase.GetAllowanceUseCase import com.tangem.domain.transaction.usecase.SendTransactionUseCase -import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase -import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetUserWalletUseCase @@ -69,6 +66,7 @@ import com.tangem.features.staking.impl.presentation.state.transformers.validato import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount import com.tangem.features.staking.impl.presentation.state.utils.isSingleAction import com.tangem.features.staking.impl.presentation.state.utils.withStubUnstakeAction +import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.Provider import com.tangem.utils.coroutines.* import com.tangem.utils.extensions.isSingleItem @@ -87,7 +85,7 @@ import kotlin.properties.Delegates @Suppress("LargeClass", "TooManyFunctions", "LongParameterList") @Stable -@ComponentScoped +@ModelScoped internal class StakingModel @Inject constructor( paramsContainer: ParamsContainer, private val stateController: StakingStateController, @@ -106,7 +104,6 @@ internal class StakingModel @Inject constructor( private val getCardInfoUseCase: GetCardInfoUseCase, private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase, private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase, - private val validateTransactionUseCase: ValidateTransactionUseCase, private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase, private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase, private val isAnyTokenStakedUseCase: IsAnyTokenStakedUseCase, @@ -243,7 +240,7 @@ internal class StakingModel @Inject constructor( return } isInitialInfoStep && noBalanceState -> { - stateController.update( + val list = buildList { SetConfirmationStateInitTransformer( isEnter = true, isExplicitExit = false, @@ -252,8 +249,27 @@ internal class StakingModel @Inject constructor( stakingApproval = stakingApproval, stakingAllowance = stakingAllowance, yieldArgs = yield.args, - ), - ) + ).let(::add) + if (BlockchainUtils.isSkipAmountEnter(uiState.value.cryptoCurrencyBlockchainId)) { + ValidatorSelectChangeTransformer( + selectedValidator = yield.preferredValidators.firstOrNull(), + yield = yield, + ).let(::add) + SetAmountDataTransformer( + clickIntents = this@StakingModel, + cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, + userWalletProvider = Provider { userWallet }, + appCurrencyProvider = Provider { appCurrency }, + ).let(::add) + AmountMaxValueStateTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatus, + minimumTransactionAmount = minimumTransactionAmount, + actionType = uiState.value.actionType, + yield = yield, + ).let(::add) + } + } + stateController.updateAll(*list.toTypedArray()) } } stakingStateRouter.onNextClick() @@ -406,7 +422,7 @@ internal class StakingModel @Inject constructor( StakingEvent.ShowAlert(StakingAlertUM.NoAvailableValidators), ) } else { - if (uiState.value.actionType == StakingActionCommonType.Enter) { + if (uiState.value.actionType is StakingActionCommonType.Enter) { stateController.updateAll( ValidatorSelectChangeTransformer( selectedValidator = null, @@ -649,16 +665,6 @@ internal class StakingModel @Inject constructor( } else { null } - val validation = amount?.let { - validateTransactionUseCase( - userWalletId = userWalletId, - amount = amount.convertToSdkAmount(cryptoCurrencyStatus.currency), - fee = feeState?.fee, - memo = null, - destination = "", - network = cryptoCurrencyStatus.currency.network, - ).leftOrNull() - } val balanceAfterTransaction = calculateBalanceAfterTransaction( amount = amount.orZero(), @@ -679,7 +685,6 @@ internal class StakingModel @Inject constructor( appCurrencyProvider = Provider { appCurrency }, feeCryptoCurrencyStatus = feeCryptoCurrencyStatus, currencyWarning = currencyWarning, - validatorError = validation, currencyCheck = currencyStatus, isSubtractAvailable = isAmountSubtractAvailable, feeError = feeError, @@ -728,7 +733,7 @@ internal class StakingModel @Inject constructor( ): BigDecimal? { // TODO split for different networks val subtractedBalanceAmount = when (actionType) { - StakingActionCommonType.Enter -> checkAndCalculateSubtractedAmount( + is StakingActionCommonType.Enter -> checkAndCalculateSubtractedAmount( isAmountSubtractAvailable = isAmountSubtractAvailable, cryptoCurrencyStatus = cryptoCurrencyStatus, amountValue = amount.orZero(), @@ -951,7 +956,7 @@ internal class StakingModel @Inject constructor( when { isInitState() -> { updateInitialData(status) - balanceUpdater.initialUpdate() + balanceUpdater.partialUpdate() } isAssentState() -> { getFee() @@ -1043,7 +1048,7 @@ internal class StakingModel @Inject constructor( } private fun isExplicitExit(balanceType: BalanceType, pendingAction: PendingAction?): Boolean { - return balanceType == BalanceType.STAKED && pendingAction?.type != StakingActionType.RESTAKE + return balanceType == BalanceType.STAKED && pendingAction?.type?.isRestake == false } private fun isAssentState(): Boolean { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt index 94cd29f646..f795c3195d 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt @@ -7,7 +7,6 @@ import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.RewardBlockType import com.tangem.domain.staking.model.stakekit.Yield import kotlinx.collections.immutable.ImmutableList -import org.joda.time.DateTime import java.math.BigDecimal internal sealed class InnerYieldBalanceState { @@ -38,5 +37,4 @@ internal data class BalanceState( val validator: Yield.Validator?, val pendingActions: ImmutableList, val isPending: Boolean, - val date: DateTime, ) \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt index d9fd203280..ef6fe9940e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt @@ -29,6 +29,14 @@ internal object StakingNotification { title = resourceReference(R.string.common_error), subtitle = subtitle, ) + + data class CardanoMinimumBalance( + val title: TextReference, + val subtitle: TextReference, + ) : StakingNotification.Error( + title = title, + subtitle = subtitle, + ) } sealed class Warning( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt index e66c38a778..328bf590dd 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt @@ -85,7 +85,7 @@ internal class StakingStateController @Inject constructor( isBalanceHidden = false, event = consumedEvent(), bottomSheetConfig = null, - actionType = StakingActionCommonType.Enter, + actionType = StakingActionCommonType.Enter(skipEnterAmount = false), buttonsState = NavigationButtonsState.Empty, balanceState = null, ) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt index 60afd5cfef..622c27808f 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt @@ -23,11 +23,11 @@ internal class StakingStateRouter( fun onNextClick() { when (stateController.value.currentStep) { StakingStep.InitialInfo -> when (val actionType = stateController.value.actionType) { - StakingActionCommonType.Enter -> showAmount() - is StakingActionCommonType.Exit -> if (actionType.partiallyUnstakeDisabled) { - showConfirmation() - } else { - showAmount() + is StakingActionCommonType.Enter -> { + if (actionType.skipEnterAmount) showConfirmation() else showAmount() + } + is StakingActionCommonType.Exit -> { + if (actionType.partiallyUnstakeDisabled) showConfirmation() else showAmount() } StakingActionCommonType.Pending.Other, StakingActionCommonType.Pending.Rewards, @@ -53,7 +53,9 @@ internal class StakingStateRouter( -> showInitial() StakingStep.Confirmation -> { when (val actionType = uiState.actionType) { - StakingActionCommonType.Enter -> showAmount() + is StakingActionCommonType.Enter -> { + if (actionType.skipEnterAmount) showInitial() else showAmount() + } is StakingActionCommonType.Pending -> showInitial() is StakingActionCommonType.Exit -> { if (actionType.partiallyUnstakeDisabled) showInitial() else showAmount() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt index 1449a49895..dc8db5482a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt @@ -62,7 +62,6 @@ internal class BalanceItemConverter( pendingActions = value.pendingActions.toPersistentList(), isClickable = value.type.isClickable() && !value.isPending, isPending = value.isPending, - date = value.date ?: DateTime.now(), ) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt index d7a73fa6f9..4fefa8ca21 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt @@ -16,7 +16,6 @@ import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toPersistentList -import org.joda.time.DateTime import java.math.BigDecimal internal class RewardsValidatorStateConverter( @@ -92,7 +91,6 @@ internal class RewardsValidatorStateConverter( isClickable = true, type = balance.type, isPending = balance.isPending, - date = balance.date ?: DateTime.now(), ) } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt index b1058db489..d86e6cc5f3 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt @@ -10,6 +10,8 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import com.tangem.domain.wallets.models.UserWallet +import com.tangem.features.txhistory.TxHistoryFeatureToggles +import com.tangem.features.txhistory.entity.TxHistoryContentUpdateEmitter import com.tangem.utils.coroutines.DelayedWork import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -24,6 +26,8 @@ internal class StakingBalanceUpdater @AssistedInject constructor( private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, private val getTxHistoryItemsUseCase: GetTxHistoryItemsUseCase, private val fetchActionsUseCase: FetchActionsUseCase, + private val txHistoryFeatureToggles: TxHistoryFeatureToggles, + private val txHistoryContentUpdateEmitter: TxHistoryContentUpdateEmitter, @DelayedWork private val coroutineScope: CoroutineScope, @Assisted private val userWallet: UserWallet, @Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus, @@ -57,24 +61,14 @@ internal class StakingBalanceUpdater @AssistedInject constructor( } suspend fun partialUpdate() { - coroutineScope { - listOf( - async { - updateNetworkStatuses(delay = 0) - }, - async { - updateProcessingActions() - }, - ).awaitAll() - } - } - - suspend fun initialUpdate() { coroutineScope { listOf( async { updateStakeBalance() }, + async { + updateNetworkStatuses(delay = 0) + }, async { updateProcessingActions() }, @@ -107,11 +101,15 @@ internal class StakingBalanceUpdater @AssistedInject constructor( ) txHistoryItemsCountEither.onRight { - getTxHistoryItemsUseCase( - userWalletId = userWallet.walletId, - currency = cryptoCurrencyStatus.currency, - refresh = true, - ) + if (txHistoryFeatureToggles.isFeatureEnabled) { + txHistoryContentUpdateEmitter.triggerUpdate() + } else { + getTxHistoryItemsUseCase( + userWalletId = userWallet.walletId, + currency = cryptoCurrencyStatus.currency, + refresh = true, + ) + } } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt index 6ed1669b40..1688f31bdf 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt @@ -63,7 +63,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor( val validatorAddress = validatorState.chosenValidator.address - val isEnter = state.actionType == StakingActionCommonType.Enter + val isEnter = state.actionType is StakingActionCommonType.Enter val isApprovalNeeded = confirmationState.isApprovalNeeded val isAllowanceNotEnough = confirmationState.allowance < amount if (isEnter && isApprovalNeeded && isAllowanceNotEnough) { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt index cc4b4686a5..27d87225e0 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt @@ -248,7 +248,7 @@ internal class StakingTransactionSender @AssistedInject constructor( val txUrl = getExplorerTransactionUrlUseCase( txHash = transactionHashes.last(), networkId = cryptoCurrencyStatus.currency.network.id, - ).getOrElse { "" } + ).getOrNull() ?: "" balanceUpdater.fullUpdate() onSendSuccess(txUrl) @@ -280,7 +280,7 @@ internal class StakingTransactionSender @AssistedInject constructor( private fun getAmount(amountState: AmountState.Data, fee: Fee, reduceAmountBy: BigDecimal?): BigDecimal { val amountValue = amountState.amountTextField.cryptoAmount.value ?: error("No amount value") val feeValue = fee.amount.value ?: error("No fee value") - val isEnterAction = stateController.value.actionType == StakingActionCommonType.Enter + val isEnterAction = stateController.value.actionType is StakingActionCommonType.Enter return checkAndCalculateSubtractedAmount( isAmountSubtractAvailable = isAmountSubtractAvailable && isEnterAction, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt index 99c1cb3379..0aa07c6011 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt @@ -12,7 +12,6 @@ import com.tangem.features.staking.impl.presentation.state.BalanceState import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState import com.tangem.features.staking.impl.presentation.state.StakingStates import kotlinx.collections.immutable.persistentListOf -import org.joda.time.DateTime internal object InitialStakingStatePreview { val defaultState = StakingStates.InitialInfoState.Data( @@ -96,7 +95,6 @@ internal object InitialStakingStatePreview { type = BalanceType.STAKED, subtitle = null, isPending = false, - date = DateTime.now(), ), ), ), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetAmountDataTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetAmountDataTransformer.kt index d33a64975d..4103633ad5 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetAmountDataTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetAmountDataTransformer.kt @@ -32,7 +32,7 @@ internal class SetAmountDataTransformer( stringReference(userWalletProvider().name) } val cryptoBalanceValue = cryptoCurrencyStatusProvider().value - val (amount, fiatAmount) = if (prevState.actionType != StakingActionCommonType.Enter) { + val (amount, fiatAmount) = if (prevState.actionType !is StakingActionCommonType.Enter) { prevState.balanceState?.cryptoAmount to prevState.balanceState?.fiatAmount } else { cryptoBalanceValue.amount to cryptoBalanceValue.fiatAmount diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt index 61eb924114..6195aea430 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt @@ -10,6 +10,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.features.staking.impl.presentation.state.* import com.tangem.features.staking.impl.presentation.state.utils.getPendingActionTitle +import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.extensions.orZero import com.tangem.utils.transformer.Transformer import kotlinx.collections.immutable.ImmutableList @@ -37,7 +38,7 @@ internal class SetButtonsStateTransformer( return prevState.copy(buttonsState = buttonsState) } - private fun getPrimaryButton(prevState: StakingUiState): NavigationButton { + private fun getPrimaryButton(prevState: StakingUiState): NavigationButton? { val confirmState = prevState.confirmationState as? StakingStates.ConfirmationState.Data val innerConfirmState = confirmState?.innerState @@ -54,7 +55,7 @@ internal class SetButtonsStateTransformer( showProgress = isInProgress, isEnabled = prevState.isButtonEnabled(), onClick = { prevState.onPrimaryClick() }, - ) + ).takeIf { prevState.isPrimaryButtonVisible() } } private fun getPrevButton(prevState: StakingUiState): NavigationButton? { @@ -125,7 +126,7 @@ internal class SetButtonsStateTransformer( resourceReference(R.string.common_close) } else { when (actionType) { - StakingActionCommonType.Enter -> { + is StakingActionCommonType.Enter -> { val amount = amountState.amountTextField.cryptoAmount.value.orZero() if (confirmationState.isApprovalNeeded && confirmationState.allowance < amount) { resourceReference(R.string.give_permission_title) @@ -162,7 +163,7 @@ internal class SetButtonsStateTransformer( clickIntents.onNextClick() } else { val amount = amountState.amountTextField.cryptoAmount.value.orZero() - val isEnterAction = actionType == StakingActionCommonType.Enter + val isEnterAction = actionType is StakingActionCommonType.Enter if (isEnterAction && confirmationState.isApprovalNeeded && confirmationState.allowance < amount) { clickIntents.showApprovalBottomSheet() } else { @@ -185,6 +186,14 @@ internal class SetButtonsStateTransformer( -> true } + private fun StakingUiState.isPrimaryButtonVisible(): Boolean { + val initialState = initialInfoState as? StakingStates.InitialInfoState.Data + val hasNotStaking = initialState?.yieldBalance == InnerYieldBalanceState.Empty + val isCardano = BlockchainUtils.isCardano(cryptoCurrencyBlockchainId) + + return hasNotStaking || !(isCardano && currentStep == StakingStep.InitialInfo) + } + private fun StakingUiState.isButtonEnabled(): Boolean { return when (currentStep) { StakingStep.InitialInfo -> initialInfoState.isPrimaryButtonEnabled diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateEmptyTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateEmptyTransformer.kt index 4bf8d5ce5d..c1ab24e015 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateEmptyTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateEmptyTransformer.kt @@ -8,7 +8,7 @@ import com.tangem.utils.transformer.Transformer internal object SetConfirmationStateEmptyTransformer : Transformer { override fun transform(prevState: StakingUiState): StakingUiState { return prevState.copy( - actionType = StakingActionCommonType.Enter, + actionType = StakingActionCommonType.Enter(skipEnterAmount = false), validatorState = StakingStates.ValidatorState.Empty(), confirmationState = StakingStates.ConfirmationState.Empty(), balanceState = null, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt index b3694b4fdf..de56770b05 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt @@ -11,6 +11,7 @@ import com.tangem.features.staking.impl.presentation.state.* import com.tangem.features.staking.impl.presentation.state.utils.isCompositePendingActions import com.tangem.features.staking.impl.presentation.state.utils.isTronStakedBalance import com.tangem.lib.crypto.BlockchainUtils +import com.tangem.lib.crypto.BlockchainUtils.isSkipAmountEnter import com.tangem.utils.extensions.isPositive import com.tangem.utils.transformer.Transformer import kotlinx.collections.immutable.ImmutableList @@ -44,11 +45,17 @@ internal class SetConfirmationStateInitTransformer( override fun transform(prevState: StakingUiState): StakingUiState { val actionType = when { - isEnter -> StakingActionCommonType.Enter - isImplicitExit || isExplicitExit -> StakingActionCommonType.Exit(isPartialUnstakeDisabled(prevState)) + isEnter -> StakingActionCommonType.Enter( + skipEnterAmount = isSkipAmountEnter(prevState.cryptoCurrencyBlockchainId), + ) + isImplicitExit || isExplicitExit -> StakingActionCommonType.Exit(isPartiallyUnstakeDisabled(prevState)) else -> when (pendingAction?.type) { - StakingActionType.STAKE -> StakingActionCommonType.Enter - StakingActionType.UNSTAKE -> StakingActionCommonType.Exit(isPartialUnstakeDisabled(prevState)) + StakingActionType.STAKE -> StakingActionCommonType.Enter( + skipEnterAmount = isSkipAmountEnter(prevState.cryptoCurrencyBlockchainId), + ) + StakingActionType.UNSTAKE -> StakingActionCommonType.Exit( + partiallyUnstakeDisabled = isPartiallyUnstakeDisabled(prevState), + ) StakingActionType.CLAIM_REWARDS, StakingActionType.RESTAKE_REWARDS, -> StakingActionCommonType.Pending.Rewards @@ -73,7 +80,7 @@ internal class SetConfirmationStateInitTransformer( transactionDoneState = TransactionDoneState.Empty, isApprovalNeeded = stakingApproval is StakingApproval.Needed, allowance = stakingAllowance, - isAmountEditable = actionType == StakingActionCommonType.Enter || + isAmountEditable = actionType is StakingActionCommonType.Enter || actionType is StakingActionCommonType.Exit && !actionType.partiallyUnstakeDisabled, reduceAmountBy = null, @@ -83,7 +90,7 @@ internal class SetConfirmationStateInitTransformer( ) } - private fun isPartialUnstakeDisabled(state: StakingUiState): Boolean { + private fun isPartiallyUnstakeDisabled(state: StakingUiState): Boolean { val isSolana = BlockchainUtils.isSolana(state.cryptoCurrencyBlockchainId) val isValidatorPreferred = balanceState?.validator?.preferred == true if (isSolana && !isValidatorPreferred) { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt index 723a0ffd7c..30fd4ad88e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt @@ -4,7 +4,8 @@ import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList -import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.format.bigdecimal.fiat +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType @@ -35,14 +36,15 @@ internal class SetConfirmationStateLoadingTransformer( private fun getFooter(state: StakingUiState): TextReference { val amountState = state.amountState as? AmountState.Data - val isEnterAction = state.actionType == StakingActionCommonType.Enter + val isEnterAction = state.actionType is StakingActionCommonType.Enter val amountDecimal = amountState?.amountTextField?.fiatAmount?.value - val amountValue = BigDecimalFormatter.formatFiatAmount( - fiatAmount = amountDecimal, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ) + val amountValue = amountDecimal.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) + } val rewardSchedule = getRewardScheduleText( rewardSchedule = yield.metadata.rewardSchedule, networkId = cryptoCurrency.network.id.value, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index 67eef70501..45ee5db07e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -1,5 +1,6 @@ package com.tangem.features.staking.impl.presentation.state.transformers +import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.remove import com.tangem.common.ui.amountScreen.converters.AmountStateConverter import com.tangem.common.ui.amountScreen.models.AmountParameters @@ -27,7 +28,6 @@ import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType import com.tangem.features.staking.impl.presentation.state.converters.RewardsValidatorStateConverter import com.tangem.features.staking.impl.presentation.state.converters.YieldBalancesConverter import com.tangem.features.staking.impl.presentation.state.utils.getRewardScheduleText -import com.tangem.lib.crypto.BlockchainUtils.isPolkadot import com.tangem.utils.Provider import com.tangem.utils.isNullOrZero import com.tangem.utils.transformer.Transformer @@ -150,7 +150,8 @@ internal class SetInitialDataStateTransformer( cryptoCurrencyStatus: CryptoCurrencyStatus, ): RoundedListWithDividersItemData? { val minimumCryptoAmount = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum ?: return null - if (!isPolkadot(cryptoCurrencyStatus.currency.network.id.value)) return null + val blockchainId = cryptoCurrencyStatus.currency.network.id.value + if (!showMinimumRequirementInfo(blockchainId)) return null val formattedAmount = minimumCryptoAmount.format { crypto(cryptoCurrencyStatus.currency) } @@ -244,6 +245,10 @@ internal class SetInitialDataStateTransformer( return resourceReference(R.string.common_range, wrappedList(formattedMinApr, formattedMaxApr)) } + private fun showMinimumRequirementInfo(blockchainId: String): Boolean { + return blockchainId == Blockchain.Polkadot.id || blockchainId == Blockchain.Cardano.id + } + private companion object { val EQUALITY_THRESHOLD = BigDecimal(1E-10) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetTitleTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetTitleTransformer.kt index 46258e60a7..d16bb669e4 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetTitleTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetTitleTransformer.kt @@ -31,7 +31,7 @@ internal object SetTitleTransformer : Transformer { StakingStep.Confirmation -> { when (actionType) { - StakingActionCommonType.Enter -> resourceReference( + is StakingActionCommonType.Enter -> resourceReference( R.string.staking_title_stake, wrappedList(prevState.cryptoCurrencyName), ) @@ -53,7 +53,7 @@ internal object SetTitleTransformer : Transformer { } } - val subtitle = if (currentStep == StakingStep.Confirmation && actionType == StakingActionCommonType.Enter) { + val subtitle = if (currentStep == StakingStep.Confirmation && actionType is StakingActionCommonType.Enter) { stringReference(prevState.walletName) } else { null diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt index 655b39d571..10e96e7b32 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt @@ -16,6 +16,7 @@ import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.staking.impl.R import com.tangem.lib.crypto.BlockchainUtils.isTron +import com.tangem.utils.extensions.isPositive import com.tangem.utils.isNullOrZero import com.tangem.utils.transformer.Transformer import java.math.BigDecimal @@ -57,7 +58,7 @@ internal class AmountRequirementStateTransformer( amountState.amountTextField.isError -> amountState.amountTextField.error requirementError != null -> requirementError isIntegerOnlyError -> when (actionType) { - StakingActionCommonType.Enter -> resourceReference( + is StakingActionCommonType.Enter -> resourceReference( R.string.staking_amount_tron_integer_error, wrappedList(roundedDownCrypto), ) @@ -92,7 +93,7 @@ internal class AmountRequirementStateTransformer( if (isAlreadyErrorState || isAmountZero) return null return when (actionType) { - StakingActionCommonType.Enter -> { + is StakingActionCommonType.Enter -> { val enterRequirements = yield.args.enter.args[Yield.Args.ArgType.AMOUNT] enterRequirements?.getError(amountDecimal, R.string.staking_amount_requirement_error) } @@ -107,7 +108,7 @@ internal class AmountRequirementStateTransformer( private fun isIntegerOnlyError(amountState: AmountState.Data, actionType: StakingActionCommonType): Boolean { val cryptoAmountValue = amountState.amountTextField.cryptoAmount.value ?: return false - val isEnterOrExit = actionType == StakingActionCommonType.Enter || actionType is StakingActionCommonType.Exit + val isEnterOrExit = actionType is StakingActionCommonType.Enter || actionType is StakingActionCommonType.Exit val isTron = isTron(cryptoCurrencyStatus.currency.network.id.value) val isIntegerOnly = cryptoAmountValue.isZero() || cryptoAmountValue.remainder(BigDecimal.ONE).isZero() @@ -116,17 +117,30 @@ internal class AmountRequirementStateTransformer( } private fun AddressArgument.getError(amount: BigDecimal, @StringRes errorTextRes: Int): TextReference? { - val isExceedsRequirements = maximum?.compareTo(amount) == -1 || - minimum?.compareTo(amount) == 1 + val isExceedsMinRequirement = minimum?.compareTo(amount) == 1 + val isExceedsMaxRequirement = if (maximum?.isPositive() == true) { + maximum?.compareTo(amount) == -1 + } else { + cryptoCurrencyStatus.value.amount?.compareTo(amount) == 1 + } - return resourceReference( - errorTextRes, - wrappedList( + val errorText = when { + isExceedsMinRequirement -> { minimum.format { crypto(cryptoCurrencyStatus.currency) - }, - ), - ).takeIf { required && isExceedsRequirements } + } + } + isExceedsMaxRequirement -> { + maximum.format { + crypto(cryptoCurrencyStatus.currency) + } + } + else -> "" + } + return resourceReference( + errorTextRes, + wrappedList(errorText), + ).takeIf { required && (isExceedsMinRequirement || isExceedsMaxRequirement) } } data class Data( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt index 40dbd696c0..9f84b072a1 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt @@ -1,6 +1,5 @@ package com.tangem.features.staking.impl.presentation.state.transformers.notifications -import com.tangem.blockchain.common.transaction.Fee import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.notifications.NotificationUM import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification @@ -11,7 +10,6 @@ import com.tangem.common.ui.notifications.NotificationsFactory.addFeeUnreachable import com.tangem.common.ui.notifications.NotificationsFactory.addRentExemptionNotification import com.tangem.common.ui.notifications.NotificationsFactory.addReserveAmountErrorNotification import com.tangem.common.ui.notifications.NotificationsFactory.addTransactionLimitErrorNotification -import com.tangem.common.ui.notifications.NotificationsFactory.addValidateTransactionNotifications import com.tangem.core.ui.extensions.networkIconResId import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.staking.model.stakekit.Yield @@ -40,7 +38,6 @@ internal class AddStakingNotificationsTransformer( private val appCurrencyProvider: Provider, private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?, private val currencyWarning: CryptoCurrencyWarning?, - private val validatorError: Throwable?, private val feeError: GetFeeError?, private val currencyCheck: CryptoCurrencyCheck, private val isSubtractAvailable: Boolean, @@ -65,7 +62,7 @@ internal class AddStakingNotificationsTransformer( val feeValue = feeState?.fee?.amount?.value.orZero() val reduceAmountBy = confirmationState.reduceAmountBy.orZero() - val isEnterAction = prevState.actionType == StakingActionCommonType.Enter + val isEnterAction = prevState.actionType is StakingActionCommonType.Enter val isFeeCoverage = checkFeeCoverage( amountValue = amountValue, feeValue = feeValue, @@ -81,7 +78,7 @@ internal class AddStakingNotificationsTransformer( amountValue = amountValue, feeValue = feeValue, reduceAmountBy = reduceAmountBy, - ).max(minimumRequirement) + ) } else { // No amount is taken from account balance on exit or pending actions BigDecimal.ZERO @@ -216,15 +213,6 @@ internal class AddStakingNotificationsTransformer( appCurrency = appCurrency, cryptoCurrencyStatus = cryptoCurrencyStatus, ) - - // blockchain specific - addValidateTransactionNotifications( - dustValue = currencyCheck.dustValue.orZero(), - minAdaValue = (feeState?.fee as? Fee.CardanoToken)?.minAdaValue, - validationError = validatorError, - cryptoCurrency = cryptoCurrency, - onReduceClick = prevState.clickIntents::onAmountReduceToClick, - ) } private fun MutableList.addStakeExceedBalanceNotification( @@ -240,7 +228,7 @@ internal class AddStakingNotificationsTransformer( val showNotification = sendingAmount + feeAmount > balance if (showNotification) { - val notification = if (actionType == StakingActionCommonType.Enter) { + val notification = if (actionType is StakingActionCommonType.Enter) { NotificationUM.Error.TotalExceedsBalance } else { with(cryptoCurrencyStatus.currency) { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt index aed53b2408..ddff3c0e73 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt @@ -14,6 +14,7 @@ import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.state.StakingNotification import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.features.staking.impl.presentation.state.StakingUiState +import com.tangem.lib.crypto.BlockchainUtils.isCardano import com.tangem.lib.crypto.BlockchainUtils.isCosmos import com.tangem.lib.crypto.BlockchainUtils.isTron import com.tangem.utils.Provider @@ -44,9 +45,12 @@ internal class StakingInfoNotificationsFactory( addStakingLowBalanceNotification(prevState, actionAmount) when (prevState.actionType) { - StakingActionCommonType.Enter -> addEnterInfoNotifications(sendingAmount, feeValue) + is StakingActionCommonType.Enter -> addEnterInfoNotifications(sendingAmount, feeValue) is StakingActionCommonType.Exit -> addExitInfoNotifications() - is StakingActionCommonType.Pending -> addPendingInfoNotifications(prevState) + is StakingActionCommonType.Pending -> { + addCardanoRestakeMinimumAmountNotification(feeValue) + addPendingInfoNotifications(prevState) + } } } @@ -70,7 +74,9 @@ internal class StakingInfoNotificationsFactory( sendingAmount: BigDecimal, feeValue: BigDecimal, ) { + addCardanoStakeMinimumAmountNotification(feeValue) addTronRevoteNotification() + addCardanoStakeNotification() addStakingEntireBalanceNotification(sendingAmount, feeValue) } @@ -149,6 +155,48 @@ internal class StakingInfoNotificationsFactory( } } + private fun MutableList.addCardanoStakeNotification() { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val isCardano = isCardano(cryptoCurrencyStatus.currency.network.id.value) + + if (isCardano) { + add( + StakingNotification.Info.Ordinary( + title = resourceReference(R.string.staking_notification_additional_ada_deposit_title), + text = resourceReference(R.string.staking_notification_additional_ada_deposit_text), + ), + ) + } + } + + private fun MutableList.addCardanoStakeMinimumAmountNotification(feeValue: BigDecimal) { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val isCardano = isCardano(cryptoCurrencyStatus.currency.network.id.value) + val balance = cryptoCurrencyStatus.value.amount.orZero() + if (isCardano && balance - feeValue < MINIMUM_STAKE_BALANCE) { + add( + StakingNotification.Error.CardanoMinimumBalance( + title = resourceReference(R.string.staking_notification_minimum_balance_title), + subtitle = resourceReference(R.string.staking_notification_minimum_stake_ada_text), + ), + ) + } + } + + private fun MutableList.addCardanoRestakeMinimumAmountNotification(feeValue: BigDecimal) { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val isCardano = isCardano(cryptoCurrencyStatus.currency.network.id.value) + val balance = cryptoCurrencyStatus.value.amount.orZero() + if (isCardano && balance - feeValue < MINIMUM_RESTAKE_BALANCE) { + add( + StakingNotification.Error.CardanoMinimumBalance( + title = resourceReference(R.string.staking_notification_minimum_restake_ada_title), + subtitle = resourceReference(R.string.staking_notification_minimum_restake_ada_text), + ), + ) + } + } + private fun MutableList.addStakingEntireBalanceNotification( sendingAmount: BigDecimal, feeValue: BigDecimal, @@ -158,7 +206,7 @@ internal class StakingInfoNotificationsFactory( val isEntireBalance = sendingAmount.plus(feeValue) == balance - if (isEntireBalance && isSubtractAvailable) { + if (isEntireBalance && isSubtractAvailable && !isCardano(cryptoCurrencyStatus.currency.network.id.value)) { add(StakingNotification.Info.StakeEntireBalance) } } @@ -179,4 +227,9 @@ internal class StakingInfoNotificationsFactory( add(StakingNotification.Warning.LowStakedBalance) } } + + private companion object { + val MINIMUM_STAKE_BALANCE = "5".toBigDecimal() + val MINIMUM_RESTAKE_BALANCE = "3".toBigDecimal() + } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt index 0f79a3ab26..960c928ca8 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt @@ -18,7 +18,7 @@ internal class ValidatorSelectChangeTransformer( val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data val isRestake = prevState.actionType == StakingActionCommonType.Pending.Restake - val isEnter = prevState.actionType == StakingActionCommonType.Enter + val isEnter = prevState.actionType is StakingActionCommonType.Enter val isFromInfoScreen = prevState.currentStep == StakingStep.InitialInfo val isVoteLocked = confirmationState?.pendingAction?.type == StakingActionType.VOTE_LOCKED diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt index 9287657626..70f2a487aa 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt @@ -7,6 +7,7 @@ import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.action.StakingActionType import com.tangem.features.staking.impl.presentation.state.BalanceState import com.tangem.lib.crypto.BlockchainUtils.isBSC +import com.tangem.lib.crypto.BlockchainUtils.isCardano import com.tangem.lib.crypto.BlockchainUtils.isSolana import com.tangem.lib.crypto.BlockchainUtils.isTron import kotlinx.collections.immutable.ImmutableList @@ -37,14 +38,12 @@ internal fun StakingActionType?.getPendingActionTitle(): TextReference = when (t internal fun isSingleAction(networkId: String, activeStake: BalanceState): Boolean { val isSingleAction = activeStake.pendingActions.size <= 1 // Either single or none pending actions val isCompositePendingActions = isCompositePendingActions(networkId, activeStake.pendingActions) - val isBscRestake = isBSC(networkId) && activeStake.pendingActions.any { - it.type == StakingActionType.RESTAKE - } + val isRestake = activeStake.pendingActions.any { it.type.isRestake } - return isSingleAction && !isBscRestake || isCompositePendingActions + return isSingleAction && !isRestake || isCompositePendingActions } -internal fun withStubUnstakeAction(networkId: String, activeStake: BalanceState) = if (isBSC(networkId)) { +internal fun withStubUnstakeAction(networkId: String, activeStake: BalanceState) = if (isStubUnstakeAction(networkId)) { activeStake.pendingActions.plus( PendingAction( type = StakingActionType.UNSTAKE, @@ -65,4 +64,8 @@ internal fun isCompositePendingActions(networkId: String, pendingActions: Immuta isSolana(networkId) -> pendingActions?.any { it.type == StakingActionType.WITHDRAW } == true else -> false } +} + +private fun isStubUnstakeAction(networkId: String): Boolean { + return isBSC(networkId) || isCardano(networkId) } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt index 866458c643..49d1aa49dd 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt @@ -5,6 +5,7 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -72,6 +73,7 @@ internal fun StakingConfirmationContent( ) StakingFeeBlock(feeState = state.feeState, isTransactionSent = isTransactionSent) NotificationsBlock(notifications = state.notifications) + Spacer(Modifier) } } diff --git a/features/stories/impl/src/main/java/com/tangem/feature/stories/impl/di/StoriesModelModule.kt b/features/stories/impl/src/main/java/com/tangem/feature/stories/impl/di/StoriesModelModule.kt index de6c20e886..f686729341 100644 --- a/features/stories/impl/src/main/java/com/tangem/feature/stories/impl/di/StoriesModelModule.kt +++ b/features/stories/impl/src/main/java/com/tangem/feature/stories/impl/di/StoriesModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.feature.stories.impl.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.feature.stories.impl.model.StoriesModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface StoriesModelModule { @Binds diff --git a/features/swap/data/build.gradle.kts b/features/swap/data/build.gradle.kts index f9af5bb02d..0134f08d66 100644 --- a/features/swap/data/build.gradle.kts +++ b/features/swap/data/build.gradle.kts @@ -5,6 +5,7 @@ plugins { alias(deps.plugins.kotlin.android) alias(deps.plugins.kotlin.kapt) alias(deps.plugins.hilt.android) + alias(deps.plugins.ksp) id("configuration") } @@ -29,7 +30,7 @@ dependencies { implementation(deps.moshi) implementation(deps.moshi.kotlin) implementation(deps.arrow.core) - kaptForObfuscatingVariants(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) kaptForObfuscatingVariants(deps.retrofit.response.type.keeper) /** Domain */ @@ -48,6 +49,7 @@ dependencies { /** Others */ implementation(deps.timber) + implementation(deps.jodatime) /** DI */ implementation(deps.hilt.android) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt index cb3ba18d9f..a1cd69d8cb 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt @@ -17,6 +17,8 @@ internal class ExchangeStatusConverter : Converter createdAt + } else { + false + } +} @JsonClass(generateAdapter = false) enum class ExchangeStatus { diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 7cc9ebc2f2..717a096159 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -989,6 +989,8 @@ internal class SwapInteractorImpl @AssistedInject constructor( Bitrock, BitrockTestnet, Sonic, SonicTestnet, ApeChain, ApeChainTestnet, + Scroll, ScrollTestnet, + ZkLinkNova, ZkLinkNovaTestnet, -> Fee.Common(feeAmount) // endregion } @@ -1004,6 +1006,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( timestamp: Long, txExternalUrl: String? = null, txExternalId: String? = null, + averageDuration: Int? = null, ) { swapTransactionRepository.storeTransaction( userWalletId = userWalletId, @@ -1021,6 +1024,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( txId = swapDataModel.transaction.txId, txExternalUrl = txExternalUrl, txExternalId = txExternalId, + averageDuration = averageDuration, ), ), ) diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/di/SwapModelModule.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/di/SwapModelModule.kt index 911918e2b6..4a40e11ce5 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/di/SwapModelModule.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/di/SwapModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.feature.swap.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.feature.swap.model.SwapModel import dagger.Binds @@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface SwapModelModule { @Binds diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index f5a23e89f8..1728183620 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -12,7 +12,7 @@ import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState.I import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.url.UrlOpener @@ -76,7 +76,7 @@ typealias SuccessLoadedSwapData = Map @Suppress("LongParameterList", "LargeClass") @Stable -@ComponentScoped +@ModelScoped internal class SwapModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/AutosizeTextField.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/AutosizeTextField.kt index 0033a1971b..2a6b4ed8ea 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/AutosizeTextField.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/AutosizeTextField.kt @@ -7,7 +7,7 @@ import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.selection.LocalTextSelectionColors import androidx.compose.foundation.text.selection.TextSelectionColors -import androidx.compose.material.Text +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt index 44fae2d1f8..e8078c5bee 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt @@ -5,8 +5,8 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding -import androidx.compose.material.Icon -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt index c06a97f04e..6da65023c5 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt @@ -8,8 +8,8 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material.Scaffold -import androidx.compose.material.Text +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index 81a94f3702..81430c0cfa 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -10,10 +10,9 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text import androidx.compose.material3.ripple import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -195,7 +194,7 @@ private fun Header(type: TransactionCardType, balance: String, modifier: Modifie text = type.header.resolveReference(), color = titleColor, maxLines = 1, - style = MaterialTheme.typography.subtitle2, + style = TangemTheme.typography.subtitle2, modifier = Modifier .align(Alignment.CenterVertically), ) @@ -205,7 +204,7 @@ private fun Header(type: TransactionCardType, balance: String, modifier: Modifie Text( text = it, color = TangemTheme.colors.text.tertiary, - style = MaterialTheme.typography.body2, + style = TangemTheme.typography.body2, modifier = Modifier .align(Alignment.CenterVertically), ) diff --git a/features/tokendetails/api/build.gradle.kts b/features/tokendetails/api/build.gradle.kts index 26d2527cd2..dc22e4ffbb 100644 --- a/features/tokendetails/api/build.gradle.kts +++ b/features/tokendetails/api/build.gradle.kts @@ -10,8 +10,11 @@ android { } dependencies { - implementation(projects.domain.tokens.models) + /** Core */ + implementation(projects.core.decompose) + implementation(projects.core.ui) - /** AndroidX */ - implementation(deps.androidx.fragment.ktx) + /** Domain models */ + implementation(projects.domain.tokens.models) + implementation(projects.domain.wallets.models) } \ No newline at end of file diff --git a/features/tokendetails/api/src/main/kotlin/com/tangem/features/tokendetails/TokenDetailsComponent.kt b/features/tokendetails/api/src/main/kotlin/com/tangem/features/tokendetails/TokenDetailsComponent.kt new file mode 100644 index 0000000000..a057fcb984 --- /dev/null +++ b/features/tokendetails/api/src/main/kotlin/com/tangem/features/tokendetails/TokenDetailsComponent.kt @@ -0,0 +1,16 @@ +package com.tangem.features.tokendetails + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId + +interface TokenDetailsComponent : ComposableContentComponent { + + data class Params( + val userWalletId: UserWalletId, + val currency: CryptoCurrency, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/tokendetails/api/src/main/kotlin/com/tangem/features/tokendetails/navigation/TokenDetailsRouter.kt b/features/tokendetails/api/src/main/kotlin/com/tangem/features/tokendetails/navigation/TokenDetailsRouter.kt deleted file mode 100644 index a361d75438..0000000000 --- a/features/tokendetails/api/src/main/kotlin/com/tangem/features/tokendetails/navigation/TokenDetailsRouter.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.features.tokendetails.navigation - -import androidx.fragment.app.Fragment - -interface TokenDetailsRouter { - - fun getEntryFragment(): Fragment -} \ No newline at end of file diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index 1457affebd..e725d7833a 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -99,5 +99,6 @@ dependencies { implementation(projects.features.markets.api) implementation(projects.features.onramp.api) implementation(projects.features.swap.api) + implementation(projects.features.txhistory.api) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt new file mode 100644 index 0000000000..b89234b7b6 --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt @@ -0,0 +1,90 @@ +package com.tangem.feature.tokendetails + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.arkivanov.essenty.lifecycle.subscribe +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.child +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.deeplink.DeepLinksRegistry +import com.tangem.core.deeplink.global.BuyCurrencyDeepLink +import com.tangem.core.deeplink.utils.registerDeepLinks +import com.tangem.core.ui.components.NavigationBar3ButtonsScrim +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsModel +import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreen +import com.tangem.features.markets.token.block.TokenMarketBlockComponent +import com.tangem.features.tokendetails.TokenDetailsComponent +import com.tangem.features.txhistory.TxHistoryFeatureToggles +import com.tangem.features.txhistory.component.TxHistoryComponent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultTokenDetailsComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: TokenDetailsComponent.Params, + tokenMarketBlockComponentFactory: TokenMarketBlockComponent.Factory, + txHistoryComponentFactory: TxHistoryComponent.Factory, + txHistoryFeatureToggles: TxHistoryFeatureToggles, + deepLinksRegistry: DeepLinksRegistry, +) : TokenDetailsComponent, AppComponentContext by appComponentContext { + + private val model: TokenDetailsModel = getOrCreateModel(params) + private val txHistoryComponent = txHistoryComponentFactory.create( + context = child("txHistoryComponent"), + params = TxHistoryComponent.Params( + userWalletId = params.userWalletId, + currency = params.currency, + openExplorer = { model.onExploreClick() }, + ), + ).takeIf { txHistoryFeatureToggles.isFeatureEnabled } + + init { + lifecycle.subscribe( + onPause = model::onPause, + onResume = model::onResume, + ) + + registerDeepLinks( + registry = deepLinksRegistry, + BuyCurrencyDeepLink( + onReceive = model::onBuyCurrencyDeepLink, + ), + ) + } + + private val tokenMarketBlockComponent = params.currency.toTokenMarketParam()?.let { tokenMarketParams -> + tokenMarketBlockComponentFactory.create( + appComponentContext = child("tokenMarketBlockComponent"), + params = tokenMarketParams, + ) + } + + @Composable + override fun Content(modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + NavigationBar3ButtonsScrim() + TokenDetailsScreen( + state = state, + tokenMarketBlockComponent = tokenMarketBlockComponent, + txHistoryComponent = txHistoryComponent, + ) + } + + private fun CryptoCurrency.toTokenMarketParam(): TokenMarketBlockComponent.Params? { + id.rawCurrencyId ?: return null // token price is not available + + return TokenMarketBlockComponent.Params(cryptoCurrency = this) + } + + @AssistedFactory + interface Factory : TokenDetailsComponent.Factory { + override fun create( + context: AppComponentContext, + params: TokenDetailsComponent.Params, + ): DefaultTokenDetailsComponent + } +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/di/TokenDetailsModule.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/di/TokenDetailsModule.kt new file mode 100644 index 0000000000..356a4c6fe4 --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/di/TokenDetailsModule.kt @@ -0,0 +1,38 @@ +package com.tangem.feature.tokendetails.di + +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.model.Model +import com.tangem.feature.tokendetails.DefaultTokenDetailsComponent +import com.tangem.feature.tokendetails.presentation.router.DefaultTokenDetailsRouter +import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsModel +import com.tangem.features.tokendetails.TokenDetailsComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface TokenDetailsModule { + + @Binds + fun bindComponentFactory(factory: DefaultTokenDetailsComponent.Factory): TokenDetailsComponent.Factory + + @Binds + @IntoMap + @ClassKey(TokenDetailsModel::class) + fun bindModel(model: TokenDetailsModel): Model +} + +@Module +@InstallIn(ModelComponent::class) +internal interface StakingComponentModule { + + @Binds + @ModelScoped + fun bindRouter(impl: DefaultTokenDetailsRouter): InnerTokenDetailsRouter +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/di/TokenDetailsRouterModule.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/di/TokenDetailsRouterModule.kt deleted file mode 100644 index 94147e5592..0000000000 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/di/TokenDetailsRouterModule.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.tangem.feature.tokendetails.di - -import com.tangem.common.routing.AppRouter -import com.tangem.core.navigation.share.ShareManager -import com.tangem.core.navigation.url.UrlOpener -import com.tangem.feature.tokendetails.presentation.router.DefaultTokenDetailsRouter -import com.tangem.features.tokendetails.navigation.TokenDetailsRouter -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityComponent -import dagger.hilt.android.scopes.ActivityScoped - -@Module -@InstallIn(ActivityComponent::class) -internal object TokenDetailsRouterModule { - - @Provides - @ActivityScoped - fun provideTokenDetailsRouter( - appRouter: AppRouter, - urlOpener: UrlOpener, - shareManager: ShareManager, - ): TokenDetailsRouter { - return DefaultTokenDetailsRouter(appRouter, urlOpener, shareManager) - } -} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt deleted file mode 100644 index 2c787270d1..0000000000 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.tangem.feature.tokendetails.presentation - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.fragment.app.viewModels -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.arkivanov.decompose.defaultComponentContext -import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.AppRouter -import com.tangem.common.routing.bundle.unbundle -import com.tangem.common.routing.utils.asRouter -import com.tangem.core.decompose.context.DefaultAppComponentContext -import com.tangem.core.decompose.di.DecomposeComponent -import com.tangem.core.decompose.di.GlobalUiMessageSender -import com.tangem.core.decompose.ui.UiMessageSender -import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.components.NavigationBar3ButtonsScrim -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreen -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsViewModel -import com.tangem.features.markets.token.block.TokenMarketBlockComponent -import com.tangem.features.tokendetails.navigation.TokenDetailsRouter -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -@AndroidEntryPoint -internal class TokenDetailsFragment : ComposeFragment() { - - @Inject - override lateinit var uiDependencies: UiDependencies - - @Inject - @GlobalUiMessageSender - internal lateinit var messageSender: UiMessageSender - - @Inject - internal lateinit var tokenDetailsRouter: TokenDetailsRouter - - @Inject - internal lateinit var coroutineDispatcherProvider: CoroutineDispatcherProvider - - @Inject - internal lateinit var componentBuilder: DecomposeComponent.Builder - - @Inject - internal lateinit var tokenMarketBlockComponentFactory: TokenMarketBlockComponent.Factory - - @Inject - internal lateinit var appRouter: AppRouter - - private val viewModel by viewModels() - - private var tokenMarketBlockComponent: TokenMarketBlockComponent? = null - - private val internalTokenDetailsRouter: InnerTokenDetailsRouter - get() = requireNotNull(tokenDetailsRouter as? InnerTokenDetailsRouter) { - "internalTokenDetailsRouter should be instance of InnerTokenDetailsRouter" - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - viewModel.router = internalTokenDetailsRouter - lifecycle.addObserver(viewModel) - - val cryptoCurrency: CryptoCurrency = arguments - ?.getBundle(AppRoute.CurrencyDetails.CRYPTO_CURRENCY_KEY) - ?.unbundle(CryptoCurrency.serializer()) - ?: error("Token Details screen can't be opened without `CryptoCurrency`") - - val param = cryptoCurrency.toParam() ?: return - - val appContext = DefaultAppComponentContext( - componentContext = defaultComponentContext(requireActivity().onBackPressedDispatcher), - messageSender = messageSender, - dispatchers = coroutineDispatcherProvider, - hiltComponentBuilder = componentBuilder, - replaceRouter = appRouter.asRouter(), - ) - - tokenMarketBlockComponent = tokenMarketBlockComponentFactory.create( - appComponentContext = appContext, - params = param, - ) - } - - private fun CryptoCurrency.toParam(): TokenMarketBlockComponent.Params? { - id.rawCurrencyId ?: return null // token price is not available - - return TokenMarketBlockComponent.Params(cryptoCurrency = this) - } - - @Composable - override fun ScreenContent(modifier: Modifier) { - NavigationBar3ButtonsScrim() - TokenDetailsScreen( - state = viewModel.uiState.collectAsStateWithLifecycle().value, - tokenMarketBlockComponent = tokenMarketBlockComponent, - ) - } -} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt index f0e4b2fee5..db52ac752e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt @@ -1,22 +1,21 @@ package com.tangem.feature.tokendetails.presentation.router -import androidx.fragment.app.Fragment import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.url.UrlOpener import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.tokendetails.presentation.TokenDetailsFragment +import javax.inject.Inject -internal class DefaultTokenDetailsRouter( +@ModelScoped +internal class DefaultTokenDetailsRouter @Inject constructor( private val router: AppRouter, private val urlOpener: UrlOpener, private val shareManager: ShareManager, ) : InnerTokenDetailsRouter { - override fun getEntryFragment(): Fragment = TokenDetailsFragment() - override fun popBackStack() { router.pop() } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/InnerTokenDetailsRouter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/InnerTokenDetailsRouter.kt index 4f40d92a2b..c2555aa070 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/InnerTokenDetailsRouter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/InnerTokenDetailsRouter.kt @@ -2,9 +2,8 @@ package com.tangem.feature.tokendetails.presentation.router import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.features.tokendetails.navigation.TokenDetailsRouter -internal interface InnerTokenDetailsRouter : TokenDetailsRouter { +internal interface InnerTokenDetailsRouter { /** Pop back stack */ fun popBackStack() diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsClickIntents.kt similarity index 99% rename from features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt rename to features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsClickIntents.kt index 8806affc45..c716c21445 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsClickIntents.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels +package com.tangem.feature.tokendetails.presentation.tokendetails.model import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel import com.tangem.core.ui.extensions.TextReference diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt similarity index 90% rename from features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt rename to features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index 535d369528..6e167e2833 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -1,21 +1,20 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels +package com.tangem.feature.tokendetails.presentation.tokendetails.model -import android.os.Bundle -import androidx.lifecycle.* +import androidx.compose.runtime.Stable import androidx.paging.cachedIn import arrow.core.getOrElse import com.tangem.blockchain.common.address.AddressType import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter -import com.tangem.common.routing.bundle.unbundle import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.decompose.di.GlobalUiMessageSender +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.ui.UiMessageSender -import com.tangem.core.deeplink.DeepLinksRegistry -import com.tangem.core.deeplink.global.BuyCurrencyDeepLink import com.tangem.core.navigation.share.ShareManager import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel @@ -71,10 +70,12 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDeta import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express.ExpressStatusFactory import com.tangem.features.onramp.OnrampFeatureToggles +import com.tangem.features.tokendetails.TokenDetailsComponent import com.tangem.features.tokendetails.impl.R +import com.tangem.features.txhistory.TxHistoryFeatureToggles +import com.tangem.features.txhistory.entity.TxHistoryContentUpdateEmitter import com.tangem.utils.Provider import com.tangem.utils.coroutines.* -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.async @@ -85,9 +86,10 @@ import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList", "LargeClass", "TooManyFunctions") -@HiltViewModel -internal class TokenDetailsViewModel @Inject constructor( - private val dispatchers: CoroutineDispatcherProvider, +@Stable +@ModelScoped +internal class TokenDetailsModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, @@ -118,28 +120,21 @@ internal class TokenDetailsViewModel @Inject constructor( private val onrampFeatureToggles: OnrampFeatureToggles, private val shareManager: ShareManager, @GlobalUiMessageSender private val uiMessageSender: UiMessageSender, + private val txHistoryFeatureToggles: TxHistoryFeatureToggles, + private val txHistoryContentUpdateEmitter: TxHistoryContentUpdateEmitter, + paramsContainer: ParamsContainer, expressStatusFactory: ExpressStatusFactory.Factory, getUserWalletUseCase: GetUserWalletUseCase, getStakingIntegrationIdUseCase: GetStakingIntegrationIdUseCase, - deepLinksRegistry: DeepLinksRegistry, - savedStateHandle: SavedStateHandle, private val appRouter: AppRouter, -) : ViewModel(), DefaultLifecycleObserver, TokenDetailsClickIntents { + private val router: InnerTokenDetailsRouter, +) : Model(), TokenDetailsClickIntents { - private val userWalletId: UserWalletId = savedStateHandle.get(AppRoute.CurrencyDetails.USER_WALLET_ID_KEY) - ?.unbundle(UserWalletId.serializer()) - ?: error("This screen can't be opened without `UserWalletId`") + private val params = paramsContainer.require() + private val userWalletId: UserWalletId = params.userWalletId + private val cryptoCurrency: CryptoCurrency = params.currency - private val cryptoCurrency: CryptoCurrency = - savedStateHandle.get(AppRoute.CurrencyDetails.CRYPTO_CURRENCY_KEY) - ?.unbundle(CryptoCurrency.serializer()) - ?: error("This screen can't be opened without `CryptoCurrency`") - - private val userWallet: UserWallet by lazy { - getUserWalletUseCase(userWalletId).getOrNull() ?: error("UserWallet not found") - } - - lateinit var router: InnerTokenDetailsRouter + private val userWallet: UserWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: error("UserWallet not found") private val marketPriceJobHolder = JobHolder() private val refreshStateJobHolder = JobHolder() @@ -191,17 +186,16 @@ internal class TokenDetailsViewModel @Inject constructor( val uiState: StateFlow = internalUiState init { - deepLinksRegistry.registerWithViewModel( - viewModel = this, - deepLinks = listOf( - BuyCurrencyDeepLink( - onReceive = ::onBuyCurrencyDeepLink, - ), - ), + analyticsEventsHandler.send( + event = TokenScreenAnalyticsEvent.DetailsScreenOpened(token = cryptoCurrency.symbol), ) + updateTopBarMenu() + initButtons() + updateContent() + handleBalanceHiding() } - private fun onBuyCurrencyDeepLink(externalTxId: String) { + fun onBuyCurrencyDeepLink(externalTxId: String) { if (onrampFeatureToggles.isFeatureEnabled) { router.openOnrampSuccess(externalTxId) } else { @@ -210,36 +204,24 @@ internal class TokenDetailsViewModel @Inject constructor( } } - override fun onCreate(owner: LifecycleOwner) { - analyticsEventsHandler.send( - event = TokenScreenAnalyticsEvent.DetailsScreenOpened(token = cryptoCurrency.symbol), - ) - updateTopBarMenu() - initButtons() - updateContent() - handleBalanceHiding(owner) - } - - override fun onPause(owner: LifecycleOwner) { + fun onPause() { expressTxStatusTaskScheduler.cancelTask() expressTxJobHolder.cancel() - super.onPause(owner) } - override fun onResume(owner: LifecycleOwner) { + fun onResume() { subscribeOnExpressTransactionsUpdates() - super.onResume(owner) } - override fun onCleared() { + override fun onDestroy() { expressTxStatusTaskScheduler.cancelTask() expressTxJobHolder.cancel() - super.onCleared() + super.onDestroy() } private fun initButtons() { // we need also init buttons before start all loading to avoid buttons blocking - viewModelScope.launch { + modelScope.launch { val currentCryptoCurrencyStatus = getCryptoCurrencySyncUseCase.invoke( userWalletId = userWalletId, cryptoCurrencyId = cryptoCurrency.id, @@ -256,18 +238,17 @@ internal class TokenDetailsViewModel @Inject constructor( private fun updateContent() { subscribeOnCurrencyStatusUpdates() subscribeOnExpressTransactionsUpdates() - updateTxHistory(refresh = false, showItemsLoading = true) + updateTxHistory(refresh = false, showItemsLoading = true, initialUpdating = true) } - private fun handleBalanceHiding(owner: LifecycleOwner) { + private fun handleBalanceHiding() { getBalanceHidingSettingsUseCase() - .flowWithLifecycle(owner.lifecycle) .onEach { internalUiState.value = stateFactory.getStateWithUpdatedHidden( isBalanceHidden = it.isBalanceHidden, ) } - .launchIn(viewModelScope) + .launchIn(modelScope) } private suspend fun updateButtons(currencyStatus: CryptoCurrencyStatus) { @@ -282,7 +263,7 @@ internal class TokenDetailsViewModel @Inject constructor( internalUiState.value = stateFactory.getManageButtonsState(actions = it.states) } .flowOn(dispatchers.main) - .launchIn(viewModelScope) + .launchIn(modelScope) .saveIn(buttonsJobHolder) } @@ -302,7 +283,7 @@ internal class TokenDetailsViewModel @Inject constructor( } private fun updateWarnings(cryptoCurrencyStatus: CryptoCurrencyStatus) { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { getCurrencyWarningsUseCase.invoke( userWalletId = userWalletId, currencyStatus = cryptoCurrencyStatus, @@ -315,13 +296,13 @@ internal class TokenDetailsViewModel @Inject constructor( notificationsAnalyticsSender.send(internalUiState.value, updatedState.notifications) internalUiState.value = updatedState } - .launchIn(viewModelScope) + .launchIn(modelScope) .saveIn(warningsJobHolder) } } private fun subscribeOnCurrencyStatusUpdates() { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { getCurrencyStatusUpdatesUseCase( userWalletId = userWalletId, currencyId = cryptoCurrency.id, @@ -339,13 +320,13 @@ internal class TokenDetailsViewModel @Inject constructor( currencyStatusAnalyticsSender.send(maybeCurrencyStatus) } .flowOn(dispatchers.main) - .launchIn(viewModelScope) + .launchIn(modelScope) .saveIn(marketPriceJobHolder) } } private fun subscribeOnExpressTransactionsUpdates() { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { expressTxStatusTaskScheduler.cancelTask() expressStatusFactory .getExpressStatuses() @@ -356,7 +337,7 @@ internal class TokenDetailsViewModel @Inject constructor( ::updateNetworkToSwapBalance, ) expressTxStatusTaskScheduler.scheduleTask( - viewModelScope, + modelScope, PeriodicTask( isDelayFirst = false, delay = EXPRESS_STATUS_UPDATE_DELAY, @@ -376,13 +357,13 @@ internal class TokenDetailsViewModel @Inject constructor( ) } .flowOn(dispatchers.main) - .launchIn(viewModelScope) + .launchIn(modelScope) .saveIn(expressTxJobHolder) } } private fun updateNetworkToSwapBalance(toCryptoCurrency: CryptoCurrency) { - viewModelScope.launch { + modelScope.launch { updateDelayedCurrencyStatusUseCase( userWalletId = userWalletId, network = toCryptoCurrency.network, @@ -395,29 +376,33 @@ internal class TokenDetailsViewModel @Inject constructor( * @param refresh - invalidate cache and get data from remote * @param showItemsLoading - show loading items placeholder. */ - private fun updateTxHistory(refresh: Boolean, showItemsLoading: Boolean) { - viewModelScope.launch(dispatchers.main) { - val txHistoryItemsCountEither = txHistoryItemsCountUseCase( - userWalletId = userWalletId, - currency = cryptoCurrency, - ) - - // if countEither is left, handling error state run inside getLoadingTxHistoryState - if (showItemsLoading || txHistoryItemsCountEither.isLeft()) { - internalUiState.value = stateFactory.getLoadingTxHistoryState( - itemsCountEither = txHistoryItemsCountEither, - pendingTransactions = internalUiState.value.pendingTxs, - ) - } - - txHistoryItemsCountEither.onRight { - val maybeTxHistory = txHistoryItemsUseCase( + private fun updateTxHistory(refresh: Boolean, showItemsLoading: Boolean, initialUpdating: Boolean = false) { + if (txHistoryFeatureToggles.isFeatureEnabled && !initialUpdating) { + modelScope.launch { txHistoryContentUpdateEmitter.triggerUpdate() } + } else { + modelScope.launch(dispatchers.main) { + val txHistoryItemsCountEither = txHistoryItemsCountUseCase( userWalletId = userWalletId, currency = cryptoCurrency, - refresh = refresh, - ).map { it.cachedIn(viewModelScope) } + ) - internalUiState.value = stateFactory.getLoadedTxHistoryState(maybeTxHistory) + // if countEither is left, handling error state run inside getLoadingTxHistoryState + if (showItemsLoading || txHistoryItemsCountEither.isLeft()) { + internalUiState.value = stateFactory.getLoadingTxHistoryState( + itemsCountEither = txHistoryItemsCountEither, + pendingTransactions = internalUiState.value.pendingTxs, + ) + } + + txHistoryItemsCountEither.onRight { + val maybeTxHistory = txHistoryItemsUseCase( + userWalletId = userWalletId, + currency = cryptoCurrency, + refresh = refresh, + ).map { it.cachedIn(modelScope) } + + internalUiState.value = stateFactory.getLoadedTxHistoryState(maybeTxHistory) + } } } } @@ -449,12 +434,12 @@ internal class TokenDetailsViewModel @Inject constructor( } } .flowOn(dispatchers.main) - .launchIn(viewModelScope) + .launchIn(modelScope) .saveIn(stakingJobHolder) } private fun updateTopBarMenu() { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { val hasDerivations = networkHasDerivationUseCase(userWallet.scanResponse, cryptoCurrency.network).getOrElse { false } @@ -474,7 +459,7 @@ internal class TokenDetailsViewModel @Inject constructor( maybeAppCurrency.getOrElse { AppCurrency.Default } } .stateIn( - scope = viewModelScope, + scope = modelScope, started = SharingStarted.Eagerly, initialValue = AppCurrency.Default, ) @@ -499,7 +484,7 @@ internal class TokenDetailsViewModel @Inject constructor( val status = cryptoCurrencyStatus ?: return if (onrampFeatureToggles.isFeatureEnabled) { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { reduxStateHolder.dispatch( TradeCryptoAction.Buy( userWallet = userWallet, @@ -511,7 +496,7 @@ internal class TokenDetailsViewModel @Inject constructor( } } else { showErrorIfDemoModeOrElse { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { reduxStateHolder.dispatch( TradeCryptoAction.Buy( userWallet = userWallet, @@ -587,7 +572,7 @@ internal class TokenDetailsViewModel @Inject constructor( return } - viewModelScope.launch { + modelScope.launch { analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ReceiveScreenOpened(cryptoCurrency.symbol)) internalUiState.value = stateFactory.getStateWithReceiveBottomSheet( @@ -614,7 +599,7 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onGenerateExtendedKey() { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { val extendedKey = getExtendedPublicKeyForCurrencyUseCase( userWalletId, cryptoCurrency.network, @@ -690,7 +675,7 @@ internal class TokenDetailsViewModel @Inject constructor( override fun onHideClick() { analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonRemoveToken(cryptoCurrency.symbol)) - viewModelScope.launch { + modelScope.launch { val hasLinkedTokens = removeCurrencyUseCase.hasLinkedTokens(userWalletId, cryptoCurrency) internalUiState.value = if (hasLinkedTokens) { stateFactory.getStateWithLinkedTokensDialog(cryptoCurrency) @@ -701,7 +686,7 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onHideConfirmed() { - viewModelScope.launch { + modelScope.launch { removeCurrencyUseCase.invoke(userWalletId, cryptoCurrency) .onLeft { Timber.e(it) } .onRight { router.popBackStack() } @@ -716,7 +701,7 @@ internal class TokenDetailsViewModel @Inject constructor( private fun openExplorer() { val currencyStatus = cryptoCurrencyStatus ?: return - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { when (val addresses = currencyStatus.value.networkAddress) { is NetworkAddress.Selectable -> { internalUiState.value = stateFactory.getStateWithChooseAddressBottomSheet(cryptoCurrency, addresses) @@ -748,7 +733,7 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onAddressTypeSelected(addressModel: AddressModel) { - viewModelScope.launch { + modelScope.launch { router.openUrl( url = getExploreUrlUseCase( userWalletId = userWalletId, @@ -773,7 +758,7 @@ internal class TokenDetailsViewModel @Inject constructor( override fun onRefreshSwipe(isRefreshing: Boolean) { internalUiState.value = stateFactory.getRefreshingState() - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { listOf( async { fetchCurrencyStatusUseCase( @@ -797,7 +782,7 @@ internal class TokenDetailsViewModel @Inject constructor( override fun onDismissBottomSheet() { when (val bsContent = internalUiState.value.bottomSheetConfig?.content) { is ExpressStatusBottomSheetConfig -> { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { expressStatusFactory.removeTransactionOnBottomSheetClosed(bsContent.value) } } @@ -827,7 +812,7 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onSwapPromoDismiss() { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { shouldShowSwapPromoTokenUseCase.neverToShow() analyticsEventsHandler.send( TokenSwapPromoAnalyticsEvent.PromotionBannerClicked( @@ -840,7 +825,7 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onSwapPromoClick() { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { shouldShowSwapPromoTokenUseCase.neverToShow() analyticsEventsHandler.send( TokenSwapPromoAnalyticsEvent.PromotionBannerClicked( @@ -871,7 +856,7 @@ internal class TokenDetailsViewModel @Inject constructor( blockchain = cryptoCurrency.network.name, ), ) - viewModelScope.launch { + modelScope.launch { retryIncompleteTransactionUseCase( userWalletId = userWalletId, currency = cryptoCurrency, @@ -912,13 +897,13 @@ internal class TokenDetailsViewModel @Inject constructor( blockchain = cryptoCurrency.network.name, ), ) - viewModelScope.launch { + modelScope.launch { internalUiState.value = stateFactory.getStateWithDismissIncompleteTransactionConfirmDialog() } } override fun onConfirmDismissIncompleteTransactionClick() { - viewModelScope.launch { + modelScope.launch { dismissIncompleteTransactionUseCase( userWalletId = userWalletId, currency = cryptoCurrency, @@ -943,7 +928,7 @@ internal class TokenDetailsViewModel @Inject constructor( blockchain = cryptoCurrency.network.name, ), ) - viewModelScope.launch(dispatchers.io) { + modelScope.launch(dispatchers.io) { associateAssetUseCase( userWalletId = userWalletId, currency = cryptoCurrency, @@ -982,7 +967,7 @@ internal class TokenDetailsViewModel @Inject constructor( override fun onDisposeExpressStatus() { val bottomSheetState = internalUiState.value.bottomSheetConfig?.content if (bottomSheetState is ExpressStatusBottomSheetConfig) { - viewModelScope.launch { + modelScope.launch { expressStatusFactory.removeTransactionOnBottomSheetClosed( expressState = bottomSheetState.value, isForceDispose = true, @@ -1001,7 +986,7 @@ internal class TokenDetailsViewModel @Inject constructor( } private fun openStaking() { - viewModelScope.launch { + modelScope.launch { val yield = getYieldUseCase.invoke( cryptoCurrencyId = cryptoCurrency.id, symbol = cryptoCurrency.symbol, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotification.kt similarity index 84% rename from features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt rename to features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotification.kt index 599abba71d..348e8e63ba 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotification.kt @@ -15,9 +15,9 @@ import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.features.tokendetails.impl.R @Immutable -internal sealed interface ExchangeStatusNotifications { +internal sealed interface ExchangeStatusNotification { - sealed class CommonNotification(val config: NotificationConfig) : ExchangeStatusNotifications + sealed class CommonNotification(val config: NotificationConfig) : ExchangeStatusNotification @Deprecated("Use one in ExpressNotificationsUM") data class NeedVerification(val onGoToProviderClick: () -> Unit) : CommonNotification( @@ -45,11 +45,23 @@ internal sealed interface ExchangeStatusNotifications { ), ) + data class LongTimeExchange(val onGoToProviderClick: () -> Unit) : CommonNotification( + config = NotificationConfig( + title = resourceReference(R.string.express_exchange_notification_long_transaction_time_title), + subtitle = resourceReference(R.string.express_exchange_notification_long_transaction_time_text), + iconResId = R.drawable.ic_alert_triangle_20, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.common_go_to_provider), + onClick = onGoToProviderClick, + ), + ), + ) + data class TokenRefunded( val cryptoCurrency: CryptoCurrency, val onReadMoreClick: () -> Unit, val onGoToTokenClick: () -> Unit, - ) : ExchangeStatusNotifications { + ) : ExchangeStatusNotification { val config = CurrencyNotificationConfig( title = resourceReference( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExchangeUM.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExchangeUM.kt index ac23c537eb..85d610e7d9 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExchangeUM.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExchangeUM.kt @@ -5,7 +5,7 @@ import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.feature.swap.domain.models.domain.ExchangeStatus import com.tangem.feature.swap.domain.models.domain.SwapProvider -import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications +import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotification import kotlinx.collections.immutable.ImmutableList internal data class ExchangeUM( @@ -13,8 +13,9 @@ internal data class ExchangeUM( val provider: SwapProvider, val activeStatus: ExchangeStatus?, val statuses: ImmutableList, - val notification: ExchangeStatusNotifications? = null, + val notification: ExchangeStatusNotification?, val showProviderLink: Boolean, val fromCryptoCurrency: CryptoCurrency, val toCryptoCurrency: CryptoCurrency, + val hasLongTime: Boolean, ) : ExpressTransactionStateUM \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt index c68c1e3be2..50052f3557 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt @@ -4,7 +4,7 @@ import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsActionButton -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.ImmutableList diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsBalanceSelectStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsBalanceSelectStateConverter.kt index b08ab69246..3425cedb3d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsBalanceSelectStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsBalanceSelectStateConverter.kt @@ -1,13 +1,14 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.format -import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.feature.tokendetails.presentation.tokendetails.state.* import com.tangem.feature.tokendetails.presentation.tokendetails.state.utils.getBalance +import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import java.math.BigDecimal @@ -31,7 +32,8 @@ internal class TokenDetailsBalanceSelectStateConverter( val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data val stakingCryptoAmount = yieldBalance?.getTotalWithRewardsStakingBalance() val stakingFiatAmount = stakingCryptoAmount?.let { cryptoCurrencyStatus.value.fiatRate?.multiply(it) } - + val includeStakingTotalBalance = + BlockchainUtils.isIncludeStakingTotalBalance(cryptoCurrencyStatus.currency.network.id.value) copy( tokenBalanceBlockState = if (tokenBalanceBlockState is TokenDetailsBalanceBlockState.Content) { tokenBalanceBlockState.copy( @@ -41,11 +43,13 @@ internal class TokenDetailsBalanceSelectStateConverter( stakingFiatAmount = stakingFiatAmount, selectedBalanceType = value.type, appCurrency = appCurrencyProvider(), + includeStaking = includeStakingTotalBalance, ), displayCryptoBalance = formatCryptoAmount( status = cryptoCurrencyStatus, stakingCryptoAmount = stakingCryptoAmount, selectedBalanceType = value.type, + includeStaking = includeStakingTotalBalance, ), ) } else { @@ -60,25 +64,26 @@ internal class TokenDetailsBalanceSelectStateConverter( stakingFiatAmount: BigDecimal?, selectedBalanceType: BalanceType, appCurrency: AppCurrency, + includeStaking: Boolean, ): String { - val fiatAmount = status.fiatAmount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN - val totalAmount = fiatAmount.getBalance(selectedBalanceType, stakingFiatAmount) + val fiatAmount = status.fiatAmount?.getBalance(selectedBalanceType, stakingFiatAmount, includeStaking) - return BigDecimalFormatter.formatFiatAmount( - fiatAmount = totalAmount, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ) + return fiatAmount.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) + } } private fun formatCryptoAmount( status: CryptoCurrencyStatus, stakingCryptoAmount: BigDecimal?, selectedBalanceType: BalanceType, + includeStaking: Boolean, ): String { - val amount = status.value.amount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN - val totalAmount = amount.getBalance(selectedBalanceType, stakingCryptoAmount) + val amount = status.value.amount?.getBalance(selectedBalanceType, stakingCryptoAmount, includeStaking) - return totalAmount.format { crypto(status.currency) } + return amount.format { crypto(status.currency) } } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index c5e9df5a98..12a86802f4 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -12,13 +12,12 @@ import com.tangem.domain.models.StatusSource import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.feature.tokendetails.presentation.tokendetails.state.BalanceType -import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockState -import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents +import com.tangem.feature.tokendetails.presentation.tokendetails.state.* import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsTxHistoryTransactionStateConverter import com.tangem.feature.tokendetails.presentation.tokendetails.state.utils.getBalance -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.lib.crypto.BlockchainUtils.isIncludeStakingTotalBalance import com.tangem.utils.Provider import com.tangem.utils.StringsSigns.DASH_SIGN import com.tangem.utils.converter.Converter @@ -88,6 +87,7 @@ internal class TokenDetailsLoadedBalanceConverter( val stakingCryptoAmount = (status.value.yieldBalance as? YieldBalance.Data)?.getTotalWithRewardsStakingBalance() val stakingFiatAmount = stakingCryptoAmount?.let { status.value.fiatRate?.multiply(it) } val isBalanceSelectorEnabled = !stakingCryptoAmount.isNullOrZero() + val includeStakingTotalBalance = isIncludeStakingTotalBalance(status.currency.network.id.value) return when (status.value) { is CryptoCurrencyStatus.NoQuote, is CryptoCurrencyStatus.Loaded, @@ -102,11 +102,13 @@ internal class TokenDetailsLoadedBalanceConverter( stakingFiatAmount, currentState.selectedBalanceType, appCurrencyProvider(), + includeStakingTotalBalance, ), displayCryptoBalance = formatCryptoAmount( status, stakingCryptoAmount, currentState.selectedBalanceType, + includeStakingTotalBalance, ), balanceSegmentedButtonConfig = currentState.balanceSegmentedButtonConfig, onBalanceSelect = clickIntents::onBalanceSelect, @@ -190,9 +192,10 @@ internal class TokenDetailsLoadedBalanceConverter( stakingFiatAmount: BigDecimal?, selectedBalanceType: BalanceType, appCurrency: AppCurrency, + includeStaking: Boolean, ): String { val fiatAmount = status.fiatAmount ?: return DASH_SIGN - val totalAmount = fiatAmount.getBalance(selectedBalanceType, stakingFiatAmount) + val totalAmount = fiatAmount.getBalance(selectedBalanceType, stakingFiatAmount, includeStaking) return totalAmount.format { fiat( @@ -206,9 +209,10 @@ internal class TokenDetailsLoadedBalanceConverter( status: CryptoCurrencyStatus, stakingCryptoAmount: BigDecimal?, selectedBalanceType: BalanceType, + includeStaking: Boolean, ): String { val amount = status.value.amount ?: return DASH_SIGN - val totalAmount = amount.getBalance(selectedBalanceType, stakingCryptoAmount) + val totalAmount = amount.getBalance(selectedBalanceType, stakingCryptoAmount, includeStaking) return totalAmount.format { crypto(status.currency) } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt index 3d9269fe05..50b4710dd8 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt @@ -13,7 +13,7 @@ import com.tangem.domain.tokens.model.warnings.KaspaWarnings import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification.* -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.features.tokendetails.impl.R import com.tangem.utils.converter.Converter import com.tangem.utils.extensions.removeBy diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt index ec6b983685..c585e6e3cd 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt @@ -26,7 +26,7 @@ import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.features.tokendetails.impl.R import com.tangem.utils.Provider import com.tangem.utils.converter.Converter diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt index 44798192e1..d4f23daa2d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt @@ -16,7 +16,7 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.feature.tokendetails.presentation.tokendetails.state.* import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsActionButton -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.features.tokendetails.impl.R import com.tangem.lib.crypto.BlockchainUtils.isBitcoin import com.tangem.utils.converter.Converter diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStakingInfoConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStakingInfoConverter.kt index befa868154..ef4a99cb22 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStakingInfoConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStakingInfoConverter.kt @@ -14,10 +14,10 @@ import com.tangem.domain.staking.model.StakingEntryInfo import com.tangem.domain.staking.model.stakekit.RewardBlockType import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.feature.tokendetails.presentation.tokendetails.state.IconState import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockUM import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.features.tokendetails.impl.R import com.tangem.lib.crypto.BlockchainUtils.isBSC import com.tangem.lib.crypto.BlockchainUtils.isSolana diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index 602b035351..d360a8883f 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -33,7 +33,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.component import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadedTxHistoryConverter import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadingTxHistoryConverter import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadingTxHistoryConverter.TokenDetailsLoadingTxHistoryModel -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.features.tokendetails.impl.R import com.tangem.utils.Provider import kotlinx.collections.immutable.toImmutableList diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt index e8e7415770..780910ba7b 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt @@ -24,10 +24,10 @@ import com.tangem.feature.swap.domain.models.domain.ExchangeStatus.Companion.isF import com.tangem.feature.swap.domain.models.domain.ExchangeStatusModel import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionModel -import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications +import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotification import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeStatusState import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.features.tokendetails.impl.R import com.tangem.utils.Provider import com.tangem.utils.converter.Converter @@ -82,17 +82,18 @@ internal class TokenDetailsSwapTransactionsStateConverter( } } val statusModel = transaction.status - val notifications = getNotification( + val notification = getNotification( status = statusModel?.status, txUrl = statusModel?.txExternalUrl, refundToken = statusModel?.refundCurrency, + hasLongTime = statusModel?.hasLongTime, ) - val showProviderLink = getShowProviderLink(notifications, transaction.status) + val showProviderLink = getShowProviderLink(notification, transaction.status) result.add( ExchangeUM( provider = transaction.provider, statuses = getStatuses(statusModel?.status), - notification = notifications, + notification = notification, activeStatus = statusModel?.status, showProviderLink = showProviderLink, fromCryptoCurrency = fromCryptoCurrency, @@ -104,6 +105,7 @@ internal class TokenDetailsSwapTransactionsStateConverter( toFiatAmount, fromFiatAmount, ), + hasLongTime = transaction.status?.hasLongTime ?: false, ), ) } @@ -111,17 +113,22 @@ internal class TokenDetailsSwapTransactionsStateConverter( return result.toPersistentList() } - fun updateTxStatus(tx: ExchangeUM, statusModel: ExchangeStatusModel?): ExchangeUM { - if (statusModel == null || tx.activeStatus == statusModel.status) { + fun updateTxStatus(tx: ExchangeUM, statusModel: ExchangeStatusModel): ExchangeUM { + if (tx.activeStatus == statusModel.status && tx.hasLongTime == statusModel.hasLongTime) { Timber.e("UpdateTxStatus isn't required. Current status isn't changed") return tx } val hasFailed = statusModel.status.isFailed() - val notifications = getNotification(statusModel.status, statusModel.txExternalUrl, statusModel.refundCurrency) - val showProviderLink = getShowProviderLink(notifications, statusModel) + val notification = getNotification( + status = statusModel.status, + txUrl = statusModel.txExternalUrl, + refundToken = statusModel.refundCurrency, + hasLongTime = statusModel.hasLongTime, + ) + val showProviderLink = getShowProviderLink(notification, statusModel) return tx.copy( activeStatus = statusModel.status, - notification = notifications, + notification = notification, statuses = getStatuses(statusModel.status, hasFailed), showProviderLink = showProviderLink, info = tx.info.copy(txExternalUrl = statusModel.txExternalUrl), @@ -184,39 +191,46 @@ internal class TokenDetailsSwapTransactionsStateConverter( status: ExchangeStatus?, txUrl: String?, refundToken: CryptoCurrency?, - ): ExchangeStatusNotifications? { - return when (status) { - ExchangeStatus.Failed, - ExchangeStatus.TxFailed, - -> { + hasLongTime: Boolean?, + ): ExchangeStatusNotification? { + return when { + status == ExchangeStatus.Failed || status == ExchangeStatus.TxFailed -> { if (txUrl == null) return null - ExchangeStatusNotifications.Failed { + ExchangeStatusNotification.Failed { analyticsEventsHandler.send( TokenExchangeAnalyticsEvent.GoToProviderFail(cryptoCurrency.symbol), ) clickIntents.onGoToProviderClick(txUrl) } } - ExchangeStatus.Verifying -> { + status == ExchangeStatus.Verifying -> { if (txUrl == null) return null - ExchangeStatusNotifications.NeedVerification { + ExchangeStatusNotification.NeedVerification { analyticsEventsHandler.send( TokenExchangeAnalyticsEvent.GoToProviderKYC(cryptoCurrency.symbol), ) clickIntents.onGoToProviderClick(txUrl) } } - ExchangeStatus.Refunded -> { + status == ExchangeStatus.Refunded -> { if (refundToken == null) { null } else { - ExchangeStatusNotifications.TokenRefunded( + ExchangeStatusNotification.TokenRefunded( cryptoCurrency = refundToken, onReadMoreClick = { clickIntents.onOpenUrlClick(url = getAboutCrossChainBridgesLink()) }, onGoToTokenClick = { clickIntents.onGoToRefundedTokenClick(refundToken) }, ) } } + status?.isTerminal == false && txUrl != null && hasLongTime == true -> { + ExchangeStatusNotification.LongTimeExchange { + analyticsEventsHandler.send( + event = TokenExchangeAnalyticsEvent.GoToProviderLongTime(cryptoCurrency.symbol), + ) + clickIntents.onGoToProviderClick(txUrl) + } + } else -> null } } @@ -239,7 +253,7 @@ internal class TokenDetailsSwapTransactionsStateConverter( statuses = persistentListOf(), ) - private fun getShowProviderLink(notifications: ExchangeStatusNotifications?, status: ExchangeStatusModel?) = + private fun getShowProviderLink(notifications: ExchangeStatusNotification?, status: ExchangeStatusModel?) = notifications == null && status?.txExternalUrl != null && status.status != ExchangeStatus.Cancelled private fun getStatuses(status: ExchangeStatus?, hasFailed: Boolean = false): ImmutableList { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt index 3bf62b35de..f9d9aed682 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt @@ -18,7 +18,7 @@ import com.tangem.feature.swap.domain.models.domain.* import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsSwapTransactionsStateConverter -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.utils.Provider import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -101,10 +101,14 @@ internal class ExchangeStatusFactory @AssistedInject constructor( } else { val statusModel = getExchangeStatus(swapTx.info.txId, swapTx.provider) - swapTransactionsStateConverter.updateTxStatus( - tx = swapTx, - statusModel = statusModel, - ) + if (statusModel != null) { + swapTransactionsStateConverter.updateTxStatus( + tx = swapTx, + statusModel = statusModel, + ) + } else { + swapTx + } } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt index 5f1c18949f..636fa5cc1f 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt @@ -12,8 +12,9 @@ import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.swap.domain.models.domain.ExchangeStatus import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState +import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotification import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.assisted.Assisted @@ -110,9 +111,7 @@ internal class ExpressStatusFactory @AssistedInject constructor( return state.copy( expressTxs = expressTxs, expressTxsToDisplay = expressTxsToDisplay, - bottomSheetConfig = currentTx?.let( - ::updateStateWithExpressStatusBottomSheet, - ) ?: config, + bottomSheetConfig = currentTx?.let(::updateStateWithExpressStatusBottomSheet) ?: config, ) } @@ -146,6 +145,12 @@ internal class ExpressStatusFactory @AssistedInject constructor( val state = currentStateProvider() val bottomSheetConfig = state.bottomSheetConfig val currentConfig = bottomSheetConfig?.content as? ExpressStatusBottomSheetConfig ?: return bottomSheetConfig + + sendLongTimeExchangeNotificationShowEvent( + expressState = expressState, + currentStateNotification = (currentConfig.value as? ExchangeUM)?.notification, + ) + return bottomSheetConfig.copy( content = if (currentConfig.value != expressState) { ExpressStatusBottomSheetConfig(expressState) @@ -167,6 +172,24 @@ internal class ExpressStatusFactory @AssistedInject constructor( } } + private fun sendLongTimeExchangeNotificationShowEvent( + expressState: ExpressTransactionStateUM, + currentStateNotification: ExchangeStatusNotification?, + ) { + val newState = expressState as? ExchangeUM + val newStateNotification = newState?.notification + if (currentStateNotification !is ExchangeStatusNotification.LongTimeExchange && + newStateNotification is ExchangeStatusNotification.LongTimeExchange + ) { + analyticsEventsHandler.send( + TokenExchangeAnalyticsEvent.LongTimeTransaction( + token = cryptoCurrency.symbol, + provider = newState.provider.name, + ), + ) + } + } + @AssistedFactory interface Factory { @Suppress("LongParameterList") diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt index 79328454af..e25e788d52 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt @@ -17,7 +17,7 @@ import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsOnrampTransactionStateConverter -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.utils.Provider import dagger.assisted.Assisted import dagger.assisted.AssistedFactory diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsLoadedTxHistoryConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsLoadedTxHistoryConverter.kt index efcc958a22..cb5e434ccf 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsLoadedTxHistoryConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsLoadedTxHistoryConverter.kt @@ -6,7 +6,7 @@ import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryListError import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import kotlinx.coroutines.flow.Flow diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsLoadingTxHistoryConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsLoadingTxHistoryConverter.kt index e7c0cd2e1b..479a510db6 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsLoadingTxHistoryConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsLoadingTxHistoryConverter.kt @@ -7,7 +7,7 @@ import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.domain.txhistory.models.TxHistoryStateError import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadingTxHistoryConverter.TokenDetailsLoadingTxHistoryModel -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toImmutableList diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt index 6a8bc163c7..2dc64a9aef 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt @@ -7,7 +7,7 @@ import com.tangem.core.ui.components.transactions.state.TxHistoryState.TxHistory import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import kotlinx.coroutines.CoroutineScope diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt index 360126d91d..8bc7cf05cd 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt @@ -12,7 +12,7 @@ import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.toTimeFormat import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryItem.* -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.features.tokendetails.impl.R import com.tangem.utils.StringsSigns.MINUS import com.tangem.utils.StringsSigns.PLUS diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/utils/BalanceUtils.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/utils/BalanceUtils.kt index fe714ff2ca..25b4f3ecc1 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/utils/BalanceUtils.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/utils/BalanceUtils.kt @@ -3,8 +3,12 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.utils import com.tangem.feature.tokendetails.presentation.tokendetails.state.BalanceType import java.math.BigDecimal -internal fun BigDecimal.getBalance(selectedBalanceType: BalanceType, stakingAmount: BigDecimal?): BigDecimal { - return if (selectedBalanceType == BalanceType.ALL && stakingAmount != null) { +internal fun BigDecimal.getBalance( + selectedBalanceType: BalanceType, + stakingAmount: BigDecimal?, + includeStaking: Boolean, +): BigDecimal { + return if (selectedBalanceType == BalanceType.ALL && stakingAmount != null && includeStaking) { this.plus(stakingAmount) } else { this diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index 29775149aa..f315753e17 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -3,16 +3,18 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui import android.content.res.Configuration import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.* import androidx.compose.material3.Scaffold import androidx.compose.material3.ScaffoldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.State import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig import com.tangem.common.ui.expressStatus.expressTransactionsItems @@ -39,11 +41,18 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.T import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheet import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.staking.TokenStakingBlock import com.tangem.features.markets.token.block.TokenMarketBlockComponent +import com.tangem.features.txhistory.component.TxHistoryComponent +import com.tangem.features.txhistory.entity.TxHistoryUM +import kotlin.reflect.KProperty // TODO: Split to blocks [REDACTED_JIRA] @Suppress("LongMethod") @Composable -internal fun TokenDetailsScreen(state: TokenDetailsState, tokenMarketBlockComponent: TokenMarketBlockComponent?) { +internal fun TokenDetailsScreen( + state: TokenDetailsState, + tokenMarketBlockComponent: TokenMarketBlockComponent?, + txHistoryComponent: TxHistoryComponent?, +) { BackHandler(onBack = state.topAppBarConfig.onBackClick) val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() } @@ -57,6 +66,8 @@ internal fun TokenDetailsScreen(state: TokenDetailsState, tokenMarketBlockCompon } else { null } + val listState = rememberLazyListState() + val txHistoryComponentState by txHistoryComponent?.txHistoryState?.collectAsStateWithLifecycle() val betweenItemsPadding = TangemTheme.dimens.spacing12 val horizontalPadding = TangemTheme.dimens.spacing16 val itemModifier = Modifier @@ -69,6 +80,7 @@ internal fun TokenDetailsScreen(state: TokenDetailsState, tokenMarketBlockCompon ) { LazyColumn( modifier = Modifier.fillMaxSize(), + state = listState, contentPadding = PaddingValues( bottom = TangemTheme.dimens.spacing16 + bottomBarHeight, ), @@ -145,9 +157,12 @@ internal fun TokenDetailsScreen(state: TokenDetailsState, tokenMarketBlockCompon ) txHistoryItems( - state = state.txHistoryState, - isBalanceHidden = state.isBalanceHidden, + listState = listState, + txHistoryComponent = txHistoryComponent, + txHistoryComponentState = txHistoryComponentState, + txHistoryState = state.txHistoryState, txHistoryItems = txHistoryItems, + isBalanceHidden = state.isBalanceHidden, ) } } @@ -170,6 +185,28 @@ internal fun TokenDetailsScreen(state: TokenDetailsState, tokenMarketBlockCompon } } +@Suppress("LongParameterList") +private fun LazyListScope.txHistoryItems( + listState: LazyListState, + txHistoryComponent: TxHistoryComponent?, + txHistoryComponentState: TxHistoryUM?, + txHistoryState: TxHistoryState, + txHistoryItems: LazyPagingItems?, + isBalanceHidden: Boolean, +) { + if (txHistoryComponent != null && txHistoryComponentState != null) { + with(txHistoryComponent) { txHistoryContent(listState = listState, state = txHistoryComponentState) } + } else { + txHistoryItems( + state = txHistoryState, + isBalanceHidden = isBalanceHidden, + txHistoryItems = txHistoryItems, + ) + } +} + +private inline operator fun State?.getValue(thisObj: Any?, property: KProperty<*>): T? = this?.value + // region Preview @Preview(showBackground = true, widthDp = 360) @Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @@ -181,6 +218,7 @@ private fun TokenDetailsScreenPreview( TokenDetailsScreen( state = state, tokenMarketBlockComponent = null, + txHistoryComponent = null, ) } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheet.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheet.kt index 8eec7243af..0bf0416539 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheet.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheet.kt @@ -1,12 +1,16 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express +import android.content.res.Configuration import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig import com.tangem.common.ui.expressStatus.OnrampStatusBottomSheetContent -import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM +import com.tangem.common.ui.expressStatus.state.* import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.exchange.ExchangeStatusBottomSheetContent @@ -21,4 +25,21 @@ internal fun ExpressStatusBottomSheet(config: TangemBottomSheetConfig) { is ExchangeUM -> ExchangeStatusBottomSheetContent(state) } } +} + +@Preview(showBackground = true, widthDp = 360, heightDp = 720) +@Preview(showBackground = true, widthDp = 360, heightDp = 720, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun PreviewExpressStatusBottomSheet( + @PreviewParameter(ExpressStatusBottomSheetStateProvider::class) param: ExpressStatusBottomSheetConfig, +) { + TangemThemePreview { + ExpressStatusBottomSheet( + config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = {}, + content = param, + ), + ) + } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheetStateProvider.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheetStateProvider.kt new file mode 100644 index 0000000000..c5ae881c94 --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheetStateProvider.kt @@ -0,0 +1,111 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.common.ui.expressStatus.state.* +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrency.ID +import com.tangem.domain.tokens.model.Network +import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType +import com.tangem.feature.swap.domain.models.domain.ExchangeStatus +import com.tangem.feature.swap.domain.models.domain.SwapProvider +import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotification +import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeStatusState +import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM +import kotlinx.collections.immutable.persistentListOf +import java.math.BigDecimal + +class ExpressStatusBottomSheetStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + ExpressStatusBottomSheetConfig( + ExchangeUM( + info = ExpressTransactionStateInfoUM( + title = TextReference.Str("Transaction Status"), + status = ExpressStatusUM( + title = TextReference.Str("Status Details"), + link = ExpressLinkUM.Empty, + statuses = persistentListOf( + ExpressStatusItemUM(TextReference.Str("Created"), ExpressStatusItemState.Active), + ExpressStatusItemUM(TextReference.Str("Exchanging"), ExpressStatusItemState.Active), + ExpressStatusItemUM(TextReference.Str("Done"), ExpressStatusItemState.Active), + ), + ), + notification = null, + txId = "123456", + txExternalId = "78910", + txExternalUrl = "https://example.com/tx/78910", + timestamp = System.currentTimeMillis(), + timestampFormatted = TextReference.Str("Just now"), + onGoToProviderClick = {}, + onClick = {}, + onDisposeExpressStatus = {}, + iconState = ExpressTransactionStateIconUM.None, + toAmount = TextReference.Str("0.1 BTC"), + toFiatAmount = TextReference.Str("$5000"), + toAmountSymbol = "BTC", + toCurrencyIcon = CurrencyIconState.Empty(), + fromAmount = TextReference.Str("5000 USDT"), + fromFiatAmount = TextReference.Str("$5000"), + fromAmountSymbol = "USDT", + fromCurrencyIcon = CurrencyIconState.Empty(), + ), + provider = SwapProvider( + providerId = "1", + rateTypes = emptyList(), + name = "Provider", + type = ExchangeProviderType.DEX, + imageLarge = "", + termsOfUse = null, + privacyPolicy = null, + slippage = BigDecimal.ZERO, + ), + activeStatus = null, + statuses = persistentListOf( + ExchangeStatusState( + status = ExchangeStatus.Exchanging, + text = stringReference("Exchanging"), + isActive = true, + isDone = false, + ), + ), + notification = ExchangeStatusNotification.LongTimeExchange {}, + showProviderLink = false, + fromCryptoCurrency = token, + toCryptoCurrency = token, + hasLongTime = true, + ), + ), + ) + + private val token + get() = CryptoCurrency.Coin( + id = ID( + ID.Prefix.COIN_PREFIX, + ID.Body.NetworkId(network.id.value), + ID.Suffix.RawID("token1"), + ), + network = network, + name = "Token 1", + symbol = "T1", + decimals = 8, + iconUrl = null, + isCustom = false, + ) + + private val network = Network( + id = Network.ID("network1"), + name = "Network One", + isTestnet = false, + standardType = Network.StandardType.ERC20, + backendId = "network1", + currencySymbol = "ETH", + derivationPath = Network.DerivationPath.None, + hasFiatFeeRate = true, + canHandleTokens = true, + transactionExtrasType = Network.TransactionExtrasType.NONE, + ) +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt index c91628293c..581f05d757 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt @@ -24,7 +24,7 @@ import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.domain.models.domain.ExchangeStatus import com.tangem.feature.swap.domain.models.domain.ExchangeStatus.Companion.isFailed -import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications +import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotification import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM @Composable @@ -88,14 +88,15 @@ internal fun ExchangeStatusBottomSheetContent(state: ExchangeUM) { } @Composable -private fun Notification(state: ExchangeStatusNotifications, activeStatus: ExchangeStatus?) { +private fun Notification(state: ExchangeStatusNotification, activeStatus: ExchangeStatus?) { AnimatedContent( targetState = state, modifier = Modifier.padding(top = TangemTheme.dimens.spacing12), label = "Exchange Status Notification Change", + contentKey = { it::class.java }, ) { notification -> when (notification) { - is ExchangeStatusNotifications.CommonNotification -> { + is ExchangeStatusNotification.CommonNotification -> { com.tangem.core.ui.components.notifications.Notification( config = notification.config, iconTint = when { @@ -106,7 +107,7 @@ private fun Notification(state: ExchangeStatusNotifications, activeStatus: Excha containerColor = TangemTheme.colors.background.action, ) } - is ExchangeStatusNotifications.TokenRefunded -> { + is ExchangeStatusNotification.TokenRefunded -> { CurrencyNotification( config = notification.config, containerColor = TangemTheme.colors.background.action, diff --git a/features/txhistory/api/.gitignore b/features/txhistory/api/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/txhistory/api/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/txhistory/api/build.gradle.kts b/features/txhistory/api/build.gradle.kts new file mode 100644 index 0000000000..7b0b2e9ff0 --- /dev/null +++ b/features/txhistory/api/build.gradle.kts @@ -0,0 +1,26 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + id("configuration") +} + +android { + namespace = "com.tangem.features.txhistory.api" +} + +dependencies { + /** Project - Core */ + implementation(projects.core.ui) + implementation(projects.core.decompose) + + /** Domain models */ + implementation(projects.domain.tokens.models) + implementation(projects.domain.wallets.models) + + /** Compose */ + implementation(deps.compose.runtime) + implementation(deps.compose.foundation) + + /** Other */ + implementation(deps.kotlin.immutable.collections) +} \ No newline at end of file diff --git a/features/txhistory/api/src/main/kotlin/com/tangem/features/txhistory/TxHistoryFeatureToggles.kt b/features/txhistory/api/src/main/kotlin/com/tangem/features/txhistory/TxHistoryFeatureToggles.kt new file mode 100644 index 0000000000..a0cb28761f --- /dev/null +++ b/features/txhistory/api/src/main/kotlin/com/tangem/features/txhistory/TxHistoryFeatureToggles.kt @@ -0,0 +1,5 @@ +package com.tangem.features.txhistory + +interface TxHistoryFeatureToggles { + val isFeatureEnabled: Boolean +} \ No newline at end of file diff --git a/features/txhistory/api/src/main/kotlin/com/tangem/features/txhistory/component/TxHistoryComponent.kt b/features/txhistory/api/src/main/kotlin/com/tangem/features/txhistory/component/TxHistoryComponent.kt new file mode 100644 index 0000000000..8fa11c9c93 --- /dev/null +++ b/features/txhistory/api/src/main/kotlin/com/tangem/features/txhistory/component/TxHistoryComponent.kt @@ -0,0 +1,28 @@ +package com.tangem.features.txhistory.component + +import androidx.compose.runtime.Stable +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.LazyListState +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.features.txhistory.entity.TxHistoryUM +import kotlinx.coroutines.flow.StateFlow + +@Stable +interface TxHistoryComponent { + + val txHistoryState: StateFlow + + fun LazyListScope.txHistoryContent(listState: LazyListState, state: TxHistoryUM) + + fun reload() + + data class Params( + val userWalletId: UserWalletId, + val currency: CryptoCurrency, + val openExplorer: () -> Unit, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/txhistory/api/src/main/kotlin/com/tangem/features/txhistory/entity/TxHistoryContentUpdateEmitter.kt b/features/txhistory/api/src/main/kotlin/com/tangem/features/txhistory/entity/TxHistoryContentUpdateEmitter.kt new file mode 100644 index 0000000000..8f8f68a500 --- /dev/null +++ b/features/txhistory/api/src/main/kotlin/com/tangem/features/txhistory/entity/TxHistoryContentUpdateEmitter.kt @@ -0,0 +1,5 @@ +package com.tangem.features.txhistory.entity + +interface TxHistoryContentUpdateEmitter { + suspend fun triggerUpdate() +} \ No newline at end of file diff --git a/features/txhistory/api/src/main/kotlin/com/tangem/features/txhistory/entity/TxHistoryUM.kt b/features/txhistory/api/src/main/kotlin/com/tangem/features/txhistory/entity/TxHistoryUM.kt new file mode 100644 index 0000000000..9a9960fc8e --- /dev/null +++ b/features/txhistory/api/src/main/kotlin/com/tangem/features/txhistory/entity/TxHistoryUM.kt @@ -0,0 +1,93 @@ +package com.tangem.features.txhistory.entity + +import com.tangem.core.ui.components.transactions.state.TransactionState +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +sealed interface TxHistoryUM { + + val isBalanceHidden: Boolean + + data class Loading(override val isBalanceHidden: Boolean, private val onExploreClick: () -> Unit) : TxHistoryUM { + val items = persistentListOf( + TxHistoryItemUM.Title(onExploreClick = onExploreClick), + TxHistoryItemUM.Transaction(TransactionState.Loading("LOADING_TX_HASH_1")), + TxHistoryItemUM.Transaction(TransactionState.Loading("LOADING_TX_HASH_2")), + TxHistoryItemUM.Transaction(TransactionState.Loading("LOADING_TX_HASH_3")), + ) + } + + /** + * Wallet transaction history state with content + */ + data class Content( + override val isBalanceHidden: Boolean, + val items: ImmutableList, + val loadMore: () -> Boolean, + ) : TxHistoryUM + + /** Empty state */ + data class Empty(override val isBalanceHidden: Boolean, val onExploreClick: () -> Unit) : TxHistoryUM + + /** + * Not supported tx history state + * + * @property pendingTransactions pending transactions + * @property onExploreClick lambda be invoke when explore button was clicked + */ + data class NotSupported( + override val isBalanceHidden: Boolean, + val pendingTransactions: ImmutableList, + val onExploreClick: () -> Unit, + ) : TxHistoryUM + + /** + * Error state + * + * @property onReloadClick lambda be invoke when reload button was clicked + */ + data class Error( + override val isBalanceHidden: Boolean, + val onReloadClick: () -> Unit, + val onExploreClick: () -> Unit, + ) : TxHistoryUM + + fun copySealed(isBalanceHidden: Boolean): TxHistoryUM { + return when (this) { + is Content -> copy(isBalanceHidden = isBalanceHidden) + is NotSupported -> copy(isBalanceHidden = isBalanceHidden) + is Empty -> copy(isBalanceHidden = isBalanceHidden) + is Error -> copy(isBalanceHidden = isBalanceHidden) + is Loading -> copy(isBalanceHidden = isBalanceHidden) + } + } + + /** Transactions history item state */ + sealed interface TxHistoryItemUM { + + /** + * Title item + * + * @property onExploreClick lambda be invoke when explore button was clicked + */ + data class Title(val onExploreClick: () -> Unit) : TxHistoryItemUM + + /** + * Group title item + * + * @property title title + * @property itemKey key to use in compose + */ + data class GroupTitle( + val title: String, + val itemKey: String, + ) : TxHistoryItemUM + + /** + * Transaction item + * + * @property state transaction state + */ + data class Transaction(val state: TransactionState) : TxHistoryItemUM + } +} \ No newline at end of file diff --git a/features/txhistory/impl/.gitignore b/features/txhistory/impl/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/txhistory/impl/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/txhistory/impl/build.gradle.kts b/features/txhistory/impl/build.gradle.kts new file mode 100644 index 0000000000..b0cc54f7ef --- /dev/null +++ b/features/txhistory/impl/build.gradle.kts @@ -0,0 +1,59 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.serialization) + alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.hilt.android) + id("configuration") +} + +android { + namespace = "com.tangem.features.txhistory.impl" +} + +dependencies { + /* Project - API */ + implementation(projects.features.txhistory.api) + + /* Project - Core */ + implementation(projects.core.decompose) + implementation(projects.core.ui) + implementation(projects.common.routing) + implementation(projects.core.configToggles) + implementation(projects.core.analytics) + implementation(projects.core.pagination) + implementation(projects.core.navigation) + + /* Project - Domain */ + implementation(projects.domain.models) + implementation(projects.domain.legacy) + implementation(projects.domain.txhistory) + implementation(projects.domain.txhistory.models) + implementation(projects.domain.wallets) + implementation(projects.domain.wallets.models) + implementation(projects.domain.tokens) + implementation(projects.domain.tokens.models) + implementation(projects.domain.balanceHiding) + implementation(projects.domain.balanceHiding.models) + + /* AndroidX */ + implementation(deps.androidx.activity.compose) + implementation(deps.lifecycle.compose) + + /* Compose */ + implementation(deps.compose.ui) + implementation(deps.compose.ui.tooling) + implementation(deps.compose.foundation) + implementation(deps.compose.material3) + implementation(deps.compose.shimmer) + + /* DI */ + implementation(deps.hilt.android) + kapt(deps.hilt.kapt) + + /* Other */ + implementation(deps.arrow.core) + implementation(deps.kotlin.immutable.collections) + implementation(deps.decompose.ext.compose) + implementation(deps.timber) +} \ No newline at end of file diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/DefaultTxHistoryFeatureToggles.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/DefaultTxHistoryFeatureToggles.kt new file mode 100644 index 0000000000..862db63ebb --- /dev/null +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/DefaultTxHistoryFeatureToggles.kt @@ -0,0 +1,11 @@ +package com.tangem.features.txhistory + +import com.tangem.core.configtoggle.feature.FeatureTogglesManager +import javax.inject.Inject + +internal class DefaultTxHistoryFeatureToggles @Inject constructor( + private val featureTogglesManager: FeatureTogglesManager, +) : TxHistoryFeatureToggles { + override val isFeatureEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled("TX_HISTORY_REFACTORING_ENABLED") +} \ No newline at end of file diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/component/DefaultTxHistoryComponent.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/component/DefaultTxHistoryComponent.kt new file mode 100644 index 0000000000..e3e25ebc5e --- /dev/null +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/component/DefaultTxHistoryComponent.kt @@ -0,0 +1,38 @@ +package com.tangem.features.txhistory.component + +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.* +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.features.txhistory.entity.TxHistoryUM +import com.tangem.features.txhistory.model.TxHistoryModel +import com.tangem.features.txhistory.ui.txHistoryItems +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.StateFlow + +internal class DefaultTxHistoryComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: TxHistoryComponent.Params, +) : TxHistoryComponent, AppComponentContext by appComponentContext { + + private val model: TxHistoryModel = getOrCreateModel(params) + + override val txHistoryState: StateFlow + get() = model.uiState + + override fun LazyListScope.txHistoryContent(listState: LazyListState, state: TxHistoryUM) { + txHistoryItems(listState, state) + } + + override fun reload() { + model.reload() + } + + @AssistedFactory + interface Factory : TxHistoryComponent.Factory { + override fun create(context: AppComponentContext, params: TxHistoryComponent.Params): DefaultTxHistoryComponent + } +} \ No newline at end of file diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/TxHistoryItemToTransactionStateConverter.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/TxHistoryItemToTransactionStateConverter.kt new file mode 100644 index 0000000000..7db10fcb7b --- /dev/null +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/TxHistoryItemToTransactionStateConverter.kt @@ -0,0 +1,129 @@ +package com.tangem.features.txhistory.converter + +import com.tangem.core.ui.components.transactions.state.TransactionState +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.utils.toTimeFormat +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.txhistory.models.TxHistoryItem +import com.tangem.features.txhistory.impl.R +import com.tangem.features.txhistory.utils.TxHistoryUiActions +import com.tangem.utils.StringsSigns +import com.tangem.utils.converter.Converter +import com.tangem.utils.extensions.isZero +import com.tangem.utils.toBriefAddressFormat + +internal class TxHistoryItemToTransactionStateConverter( + private val currency: CryptoCurrency, + private val txHistoryUiActions: TxHistoryUiActions, +) : Converter { + override fun convert(value: TxHistoryItem): TransactionState { + return TransactionState.Content( + txHash = value.txHash, + amount = value.getAmount(), + time = value.timestampInMillis.toTimeFormat(), + status = value.status.tiUiStatus(), + direction = value.extractDirection(), + iconRes = value.extractIcon(), + title = value.extractTitle(), + subtitle = value.extractSubtitle(), + timestamp = value.timestampInMillis, + onClick = { txHistoryUiActions.openTxInExplorer(value.txHash) }, + ) + } + + private fun TxHistoryItem.extractIcon(): Int = if (status == TxHistoryItem.TransactionStatus.Failed) { + R.drawable.ic_close_24 + } else { + when (type) { + is TxHistoryItem.TransactionType.Approve -> R.drawable.ic_doc_24 + is TxHistoryItem.TransactionType.Staking.Stake, + is TxHistoryItem.TransactionType.Staking.Vote, + is TxHistoryItem.TransactionType.Staking.Restake, + -> R.drawable.ic_transaction_history_staking_24 + is TxHistoryItem.TransactionType.Staking.ClaimRewards, + -> R.drawable.ic_transaction_history_claim_rewards_24 + is TxHistoryItem.TransactionType.Staking.Unstake, + is TxHistoryItem.TransactionType.Staking.Withdraw, + -> R.drawable.ic_transaction_history_unstaking_24 + is TxHistoryItem.TransactionType.Operation, + is TxHistoryItem.TransactionType.Swap, + is TxHistoryItem.TransactionType.Transfer, + is TxHistoryItem.TransactionType.UnknownOperation, + -> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24 + } + } + + private fun TxHistoryItem.extractTitle(): TextReference = when (val type = type) { + is TxHistoryItem.TransactionType.Approve -> resourceReference(R.string.common_approval) + is TxHistoryItem.TransactionType.Operation -> stringReference(type.name) + is TxHistoryItem.TransactionType.Swap -> resourceReference(R.string.common_swap) + is TxHistoryItem.TransactionType.Transfer -> resourceReference(R.string.common_transfer) + is TxHistoryItem.TransactionType.Staking.Stake -> resourceReference(R.string.common_stake) + is TxHistoryItem.TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake) + is TxHistoryItem.TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote) + is TxHistoryItem.TransactionType.Staking.ClaimRewards -> resourceReference(R.string.common_claim_rewards) + is TxHistoryItem.TransactionType.Staking.Withdraw -> resourceReference(R.string.staking_withdraw) + is TxHistoryItem.TransactionType.Staking.Restake -> resourceReference(R.string.staking_restake) + is TxHistoryItem.TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation) + } + + private fun TxHistoryItem.extractSubtitle(): TextReference = + when (val interactionAddress = interactionAddressType) { + is TxHistoryItem.InteractionAddressType.Contract -> resourceReference( + id = R.string.transaction_history_contract_address, + formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()), + ) + is TxHistoryItem.InteractionAddressType.Multiple -> resourceReference( + id = if (isOutgoing) { + R.string.transaction_history_transaction_to_address + } else { + R.string.transaction_history_transaction_from_address + }, + formatArgs = wrappedList(resourceReference(R.string.transaction_history_multiple_addresses)), + ) + is TxHistoryItem.InteractionAddressType.User -> resourceReference( + id = if (isOutgoing) { + R.string.transaction_history_transaction_to_address + } else { + R.string.transaction_history_transaction_from_address + }, + formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()), + ) + is TxHistoryItem.InteractionAddressType.Validator -> resourceReference( + id = R.string.transaction_history_transaction_validator, + formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()), + ) + null -> { + TextReference.EMPTY + } + } + + private fun TxHistoryItem.extractDirection() = + if (isOutgoing) TransactionState.Content.Direction.OUTGOING else TransactionState.Content.Direction.INCOMING + + private fun TxHistoryItem.getAmount(): String { + if (type is TxHistoryItem.TransactionType.Staking.Vote || + type == TxHistoryItem.TransactionType.Staking.ClaimRewards || + type == TxHistoryItem.TransactionType.Staking.Withdraw + ) { + return "" + } + val prefix = when { + status == TxHistoryItem.TransactionStatus.Failed -> "" + this.amount.isZero() -> "" + else -> if (isOutgoing) StringsSigns.MINUS else StringsSigns.PLUS + } + return prefix + amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + } + + private fun TxHistoryItem.TransactionStatus.tiUiStatus() = when (this) { + TxHistoryItem.TransactionStatus.Confirmed -> TransactionState.Content.Status.Confirmed + TxHistoryItem.TransactionStatus.Failed -> TransactionState.Content.Status.Failed + TxHistoryItem.TransactionStatus.Unconfirmed -> TransactionState.Content.Status.Unconfirmed + } +} \ No newline at end of file diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/di/TxHistoryFeatureModule.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/di/TxHistoryFeatureModule.kt new file mode 100644 index 0000000000..7ea832e433 --- /dev/null +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/di/TxHistoryFeatureModule.kt @@ -0,0 +1,23 @@ +package com.tangem.features.txhistory.di + +import com.tangem.features.txhistory.DefaultTxHistoryFeatureToggles +import com.tangem.features.txhistory.TxHistoryFeatureToggles +import com.tangem.features.txhistory.component.DefaultTxHistoryComponent +import com.tangem.features.txhistory.component.TxHistoryComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface TxHistoryFeatureModule { + @Binds + @Singleton + fun provideFeatureToggles(featureToggles: DefaultTxHistoryFeatureToggles): TxHistoryFeatureToggles + + @Binds + @Singleton + fun bindComponentFactory(factory: DefaultTxHistoryComponent.Factory): TxHistoryComponent.Factory +} \ No newline at end of file diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/di/TxHistoryModelModule.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/di/TxHistoryModelModule.kt new file mode 100644 index 0000000000..a968473a55 --- /dev/null +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/di/TxHistoryModelModule.kt @@ -0,0 +1,19 @@ +package com.tangem.features.txhistory.di + +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.txhistory.model.TxHistoryModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(ModelComponent::class) +internal interface TxHistoryModelModule { + @Binds + @IntoMap + @ClassKey(TxHistoryModel::class) + fun bindModel(model: TxHistoryModel): Model +} \ No newline at end of file diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/di/TxHistoryUpdaterModule.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/di/TxHistoryUpdaterModule.kt new file mode 100644 index 0000000000..e5b1f73531 --- /dev/null +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/di/TxHistoryUpdaterModule.kt @@ -0,0 +1,23 @@ +package com.tangem.features.txhistory.di + +import com.tangem.features.txhistory.entity.DefaultTxHistoryUpdater +import com.tangem.features.txhistory.entity.TxHistoryContentUpdateEmitter +import com.tangem.features.txhistory.entity.TxHistoryUpdateListener +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object TxHistoryUpdaterModule { + + @Provides + @Singleton + fun provideTxHistoryContentContentUpdateEmitter(impl: DefaultTxHistoryUpdater): TxHistoryContentUpdateEmitter = impl + + @Provides + @Singleton + fun provideTxHistoryUpdaterListener(impl: DefaultTxHistoryUpdater): TxHistoryUpdateListener = impl +} \ No newline at end of file diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/entity/DefaultTxHistoryUpdater.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/entity/DefaultTxHistoryUpdater.kt new file mode 100644 index 0000000000..fd2bf297da --- /dev/null +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/entity/DefaultTxHistoryUpdater.kt @@ -0,0 +1,18 @@ +package com.tangem.features.txhistory.entity + +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.receiveAsFlow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +internal class DefaultTxHistoryUpdater @Inject constructor() : TxHistoryUpdateListener, TxHistoryContentUpdateEmitter { + + private val updateChannel = Channel(Channel.BUFFERED) + override val updates: Flow = updateChannel.receiveAsFlow() + + override suspend fun triggerUpdate() { + updateChannel.send(Unit) + } +} \ No newline at end of file diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/entity/TxHistoryUpdateListener.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/entity/TxHistoryUpdateListener.kt new file mode 100644 index 0000000000..32dd1035a7 --- /dev/null +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/entity/TxHistoryUpdateListener.kt @@ -0,0 +1,7 @@ +package com.tangem.features.txhistory.entity + +import kotlinx.coroutines.flow.Flow + +internal interface TxHistoryUpdateListener { + val updates: Flow +} \ No newline at end of file diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/model/TxHistoryModel.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/model/TxHistoryModel.kt new file mode 100644 index 0000000000..593f171318 --- /dev/null +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/model/TxHistoryModel.kt @@ -0,0 +1,202 @@ +package com.tangem.features.txhistory.model + +import androidx.compose.runtime.Stable +import arrow.core.Either +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.navigation.url.UrlOpener +import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.tokens.error.CurrencyStatusError +import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.txhistory.models.TxHistoryStateError +import com.tangem.domain.txhistory.repository.TxHistoryRepositoryV2 +import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase +import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.features.txhistory.component.TxHistoryComponent +import com.tangem.features.txhistory.converter.TxHistoryItemToTransactionStateConverter +import com.tangem.features.txhistory.entity.TxHistoryUM +import com.tangem.features.txhistory.entity.TxHistoryUpdateListener +import com.tangem.features.txhistory.utils.TxHistoryListManager +import com.tangem.features.txhistory.utils.TxHistoryUiActions +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +@Suppress("LongParameterList") +@Stable +@ModelScoped +internal class TxHistoryModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, + private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, + private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, + private val getUserWalletUseCase: GetUserWalletUseCase, + private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, + private val urlOpener: UrlOpener, + private val txHistoryUpdateListener: TxHistoryUpdateListener, + repository: TxHistoryRepositoryV2, + paramsContainer: ParamsContainer, +) : Model(), TxHistoryUiActions { + + private val params: TxHistoryComponent.Params = paramsContainer.require() + private val txHistoryItemConverter = + TxHistoryItemToTransactionStateConverter(currency = params.currency, txHistoryUiActions = this) + private val txHistoryListManager = TxHistoryListManager( + repository = repository, + dispatchers = dispatchers, + userWalletId = params.userWalletId, + currency = params.currency, + txHistoryItemConverter = txHistoryItemConverter, + txHistoryUiActions = this, + ) + private val _uiState: MutableStateFlow = + MutableStateFlow(TxHistoryUM.Loading(isBalanceHidden = true, onExploreClick = ::openExplorer)) + val uiState: StateFlow = _uiState.asStateFlow() + + init { + handleBalanceHiding() + subscribeToUiItemChanges() + loadTxInfo() + subscribeToUpdateListener() + subscribeOnCurrencyStatusUpdates() + } + + private fun subscribeToUiItemChanges() { + txHistoryListManager.uiItems + .onEach { updateState(it) } + .launchIn(modelScope) + } + + private fun subscribeToUpdateListener() { + txHistoryUpdateListener.updates + .onEach { reload() } + .launchIn(modelScope) + } + + private fun loadTxInfo() { + _uiState.update { state -> getLoadingState(state.isBalanceHidden) } + modelScope.launch { + txHistoryItemsCountUseCase.invoke(userWalletId = params.userWalletId, currency = params.currency) + .onLeft(::handleErrorState) + .onRight { txHistoryListManager.startLoading() } + } + } + + fun reload() { + // fast exit + if (uiState.value is TxHistoryUM.NotSupported) return + + _uiState.update { state -> + if (state !is TxHistoryUM.Content) getLoadingState(state.isBalanceHidden) else state + } + modelScope.launch { + txHistoryItemsCountUseCase.invoke(userWalletId = params.userWalletId, currency = params.currency) + .onLeft(::handleErrorState) + .onRight { txHistoryListManager.reload() } + } + } + + private fun handleBalanceHiding() { + getBalanceHidingSettingsUseCase() + .onEach { _uiState.update { state -> state.copySealed(isBalanceHidden = it.isBalanceHidden) } } + .launchIn(modelScope) + } + + private fun loadMoreItems(): Boolean { + modelScope.launch { txHistoryListManager.loadMore(params.userWalletId, params.currency) } + return true + } + + private fun updateState(items: ImmutableList) { + _uiState.update { state -> + if (state is TxHistoryUM.Content) { + state.copy(items = items) + } else { + TxHistoryUM.Content( + items = items, + isBalanceHidden = state.isBalanceHidden, + loadMore = ::loadMoreItems, + ) + } + } + } + + private fun handleErrorState(error: TxHistoryStateError) { + _uiState.update { state -> + when (error) { + is TxHistoryStateError.DataError -> TxHistoryUM.Error( + isBalanceHidden = state.isBalanceHidden, + onReloadClick = ::reload, + onExploreClick = ::openExplorer, + ) + TxHistoryStateError.EmptyTxHistories -> TxHistoryUM.Empty( + isBalanceHidden = state.isBalanceHidden, + onExploreClick = ::openExplorer, + ) + TxHistoryStateError.TxHistoryNotImplemented -> TxHistoryUM.NotSupported( + isBalanceHidden = state.isBalanceHidden, + pendingTransactions = persistentListOf(), + onExploreClick = ::openExplorer, + ) + } + } + } + + private fun getLoadingState(isBalanceHidden: Boolean): TxHistoryUM.Loading { + return TxHistoryUM.Loading(isBalanceHidden = isBalanceHidden, onExploreClick = ::openExplorer) + } + + private fun subscribeOnCurrencyStatusUpdates() { + val userWallet: UserWallet = requireNotNull(getUserWalletUseCase(params.userWalletId).getOrNull()) { + "User wallet not found" + } + getCurrencyStatusUpdatesUseCase( + userWalletId = params.userWalletId, + currencyId = params.currency.id, + isSingleWalletWithTokens = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), + ) + .distinctUntilChanged() + .onEach(::handlePendingTxsChanges) + .flowOn(dispatchers.main) + .launchIn(modelScope) + } + + private fun handlePendingTxsChanges(maybeCurrencyStatus: Either) { + maybeCurrencyStatus.onRight { status -> + val pendingTxs = status.value.pendingTransactions + .map(txHistoryItemConverter::convert) + .toPersistentList() + _uiState.update { state -> + if (state is TxHistoryUM.NotSupported) { + state.copy(pendingTransactions = pendingTxs) + } else { + state + } + } + } + } + + override fun openExplorer() { + params.openExplorer() + } + + override fun openTxInExplorer(txHash: String) { + getExplorerTransactionUrlUseCase( + txHash = txHash, + networkId = params.currency.network.id, + ).fold( + ifLeft = { Timber.e(it.toString()) }, + ifRight = { urlOpener.openUrl(url = it) }, + ) + } +} \ No newline at end of file diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/ui/TxHistoryContent.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/ui/TxHistoryContent.kt new file mode 100644 index 0000000000..9525d0ede2 --- /dev/null +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/ui/TxHistoryContent.kt @@ -0,0 +1,164 @@ +package com.tangem.features.txhistory.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.tangem.core.ui.components.list.InfiniteListHandler +import com.tangem.core.ui.components.transactions.PendingTxsBlock +import com.tangem.core.ui.components.transactions.Transaction +import com.tangem.core.ui.components.transactions.TxHistoryTitle +import com.tangem.core.ui.components.transactions.empty.EmptyTransactionBlock +import com.tangem.core.ui.components.transactions.empty.EmptyTransactionsBlockState +import com.tangem.core.ui.components.transactions.state.TransactionState +import com.tangem.core.ui.decorations.roundedShapeItemDecoration +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.txhistory.entity.TxHistoryUM + +private const val LOAD_ITEMS_BUFFER = 20 + +internal fun LazyListScope.txHistoryItems(listState: LazyListState, state: TxHistoryUM) { + when (state) { + is TxHistoryUM.Content -> contentItems(listState, state) + is TxHistoryUM.Empty -> nonContentItem(state = EmptyTransactionsBlockState.Empty(state.onExploreClick)) + is TxHistoryUM.Error -> nonContentItem( + state = EmptyTransactionsBlockState.FailedToLoad( + onReload = state.onReloadClick, + onExplore = state.onExploreClick, + ), + ) + is TxHistoryUM.Loading -> loadingItems(state) + is TxHistoryUM.NotSupported -> { + if (state.pendingTransactions.isNotEmpty()) { + item(key = "PendingTxsBlock", contentType = "PendingTxsBlock") { + PendingTxsBlock(pendingTxs = state.pendingTransactions, isBalanceHidden = state.isBalanceHidden) + } + } + + nonContentItem( + state = EmptyTransactionsBlockState.NotImplemented(onExplore = state.onExploreClick), + ) + } + } +} + +private fun LazyListScope.nonContentItem(state: EmptyTransactionsBlockState, modifier: Modifier = Modifier) { + item(key = state::class.java, contentType = state::class.java) { + EmptyTransactionBlock( + state = state, + modifier = modifier + .animateItem(fadeInSpec = null, fadeOutSpec = null) + .padding(horizontal = TangemTheme.dimens.spacing16, vertical = TangemTheme.dimens.spacing12) + .fillMaxWidth(), + ) + } +} + +private fun LazyListScope.loadingItems(state: TxHistoryUM.Loading) { + itemsIndexed( + items = state.items, + key = { _, item -> + when (item) { + is TxHistoryUM.TxHistoryItemUM.GroupTitle -> item.itemKey + is TxHistoryUM.TxHistoryItemUM.Title -> item.onExploreClick.hashCode() + is TxHistoryUM.TxHistoryItemUM.Transaction -> + item.state.txHash + (item.state as? TransactionState.Content)?.hashCode() + } + }, + contentType = { _, item -> item::class.java }, + itemContent = { index, item -> + TxHistoryListItem( + state = item, + isBalanceHidden = true, + modifier = Modifier.roundedShapeItemDecoration( + currentIndex = index, + lastIndex = state.items.lastIndex, + ), + ) + }, + ) +} + +private fun LazyListScope.contentItems(listState: LazyListState, state: TxHistoryUM.Content) { + itemsIndexed( + items = state.items, + key = { _, item -> + when (item) { + is TxHistoryUM.TxHistoryItemUM.GroupTitle -> item.itemKey + is TxHistoryUM.TxHistoryItemUM.Title -> item.onExploreClick.hashCode() + is TxHistoryUM.TxHistoryItemUM.Transaction -> + item.state.txHash + (item.state as? TransactionState.Content)?.hashCode() + } + }, + contentType = { _, item -> item::class.java }, + itemContent = { index, item -> + TxHistoryListItem( + state = item, + isBalanceHidden = state.isBalanceHidden, + modifier = Modifier.roundedShapeItemDecoration( + currentIndex = index, + lastIndex = state.items.lastIndex, + ), + ) + }, + ) + item { + InfiniteListHandler( + listState = listState, + buffer = LOAD_ITEMS_BUFFER, + onLoadMore = state.loadMore, + ) + } +} + +@Composable +internal fun TxHistoryListItem( + state: TxHistoryUM.TxHistoryItemUM, + isBalanceHidden: Boolean, + modifier: Modifier = Modifier, +) { + when (state) { + is TxHistoryUM.TxHistoryItemUM.GroupTitle -> { + TxHistoryGroupTitle(config = state, modifier = modifier) + } + is TxHistoryUM.TxHistoryItemUM.Title -> { + TxHistoryTitle(onExploreClick = state.onExploreClick, modifier = modifier) + } + is TxHistoryUM.TxHistoryItemUM.Transaction -> { + Transaction( + state = state.state, + isBalanceHidden = isBalanceHidden, + modifier = modifier, + ) + } + } +} + +@Composable +private fun TxHistoryGroupTitle(config: TxHistoryUM.TxHistoryItemUM.GroupTitle, modifier: Modifier = Modifier) { + Box( + modifier = modifier + .background(TangemTheme.colors.background.primary) + .padding( + vertical = TangemTheme.dimens.spacing8, + horizontal = TangemTheme.dimens.spacing12, + ) + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size24), + contentAlignment = Alignment.CenterStart, + ) { + Text( + text = config.title, + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, + ) + } +} \ No newline at end of file diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/utils/TxHistoryListManager.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/utils/TxHistoryListManager.kt new file mode 100644 index 0000000000..91894dd1d1 --- /dev/null +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/utils/TxHistoryListManager.kt @@ -0,0 +1,99 @@ +package com.tangem.features.txhistory.utils + +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.txhistory.model.TxHistoryListBatchingContext +import com.tangem.domain.txhistory.model.TxHistoryListConfig +import com.tangem.domain.txhistory.models.PaginationWrapper +import com.tangem.domain.txhistory.models.TxHistoryItem +import com.tangem.domain.txhistory.repository.TxHistoryRepositoryV2 +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.features.txhistory.converter.TxHistoryItemToTransactionStateConverter +import com.tangem.features.txhistory.entity.TxHistoryUM +import com.tangem.pagination.BatchAction +import com.tangem.pagination.BatchListState +import com.tangem.pagination.PaginationStatus +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.JobHolder +import com.tangem.utils.coroutines.saveIn +import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.* + +private typealias TxHistoryBatchAction = BatchAction + +internal class TxHistoryListManager( + private val repository: TxHistoryRepositoryV2, + private val dispatchers: CoroutineDispatcherProvider, + private val userWalletId: UserWalletId, + private val currency: CryptoCurrency, + txHistoryItemConverter: TxHistoryItemToTransactionStateConverter, + txHistoryUiActions: TxHistoryUiActions, +) { + + private val jobHolder = JobHolder() + private val actionsFlow: MutableSharedFlow = MutableSharedFlow( + replay = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST, + ) + private val state: MutableStateFlow = MutableStateFlow(TxHistoryListState()) + private val uiManager = TxHistoryUiManager( + state = state, + txHistoryItemConverter = txHistoryItemConverter, + txHistoryUiActions = txHistoryUiActions, + ) + + val uiItems: Flow> = uiManager.items + + suspend fun startLoading() = coroutineScope { + val batchFlow = repository.getTxHistoryBatchFlow( + context = TxHistoryListBatchingContext( + actionsFlow = actionsFlow, + coroutineScope = this, + ), + batchSize = 50, + ) + + batchFlow.state + .onEach { state -> updateState(state) } + .flowOn(dispatchers.default) + .launchIn(scope = this) + .saveIn(jobHolder) + + actionsFlow.emit( + BatchAction.Reload( + requestParams = TxHistoryListConfig(userWalletId, currency, refresh = false), + ), + ) + } + + suspend fun reload() { + actionsFlow.emit( + BatchAction.Reload( + requestParams = TxHistoryListConfig(userWalletId, currency, refresh = true), + ), + ) + } + + suspend fun loadMore(userWalletId: UserWalletId, currency: CryptoCurrency) { + actionsFlow.emit( + BatchAction.LoadMore( + requestParams = TxHistoryListConfig(userWalletId, currency, refresh = false), + ), + ) + } + + private fun updateState(batchListState: BatchListState>) { + state.update { state -> + val clearUiBatches = + state.status is PaginationStatus.InitialLoading && batchListState.status is PaginationStatus.Paginating + state.copy( + status = batchListState.status, + uiBatches = uiManager.createOrUpdateUiBatches( + newCurrencyBatches = batchListState.data, + clearUiBatches = clearUiBatches, + ), + ) + } + } +} \ No newline at end of file diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/utils/TxHistoryListState.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/utils/TxHistoryListState.kt new file mode 100644 index 0000000000..8e553fe6ac --- /dev/null +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/utils/TxHistoryListState.kt @@ -0,0 +1,10 @@ +package com.tangem.features.txhistory.utils + +import com.tangem.features.txhistory.entity.TxHistoryUM +import com.tangem.pagination.Batch +import com.tangem.pagination.PaginationStatus + +data class TxHistoryListState( + val status: PaginationStatus<*> = PaginationStatus.None, + val uiBatches: List>> = listOf(), +) \ No newline at end of file diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/utils/TxHistoryUiActions.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/utils/TxHistoryUiActions.kt new file mode 100644 index 0000000000..57a50a9014 --- /dev/null +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/utils/TxHistoryUiActions.kt @@ -0,0 +1,7 @@ +package com.tangem.features.txhistory.utils + +internal interface TxHistoryUiActions { + + fun openExplorer() + fun openTxInExplorer(txHash: String) +} \ No newline at end of file diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/utils/TxHistoryUiManager.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/utils/TxHistoryUiManager.kt new file mode 100644 index 0000000000..2f4aba1080 --- /dev/null +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/utils/TxHistoryUiManager.kt @@ -0,0 +1,113 @@ +package com.tangem.features.txhistory.utils + +import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday +import com.tangem.domain.txhistory.models.PaginationWrapper +import com.tangem.domain.txhistory.models.TxHistoryItem +import com.tangem.features.txhistory.converter.TxHistoryItemToTransactionStateConverter +import com.tangem.features.txhistory.entity.TxHistoryUM +import com.tangem.pagination.Batch +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.mapLatest +import java.util.UUID + +internal class TxHistoryUiManager( + private val state: MutableStateFlow, + private val txHistoryItemConverter: TxHistoryItemToTransactionStateConverter, + private val txHistoryUiActions: TxHistoryUiActions, +) { + + @OptIn(ExperimentalCoroutinesApi::class) + val items: Flow> = state + .mapLatest { state -> + state.uiBatches.asSequence() + .flatMap { it.data } + .toImmutableList() + } + .distinctUntilChanged() + + fun createOrUpdateUiBatches( + newCurrencyBatches: List>>, + clearUiBatches: Boolean, + ): List>> { + val currentUiBatches = state.value.uiBatches + val batches = if (clearUiBatches) mutableListOf() else currentUiBatches.toMutableList() + + for ((key, data) in newCurrencyBatches) { + // Find if batch with same key exists + val existingBatchIndex = batches.indexOfFirst { it.key == key } + val shouldUpdateExisting = existingBatchIndex != -1 && + currentUiBatches[existingBatchIndex].data.transactionItemsSizeNotEqual(data.items) + + // Case 1: Update existing batch if sizes differ + if (shouldUpdateExisting) { + val items = generateUiItems(key, data) + batches[existingBatchIndex] = Batch(key = key, data = items) + continue + } + + // Case 2: Skip if batch exists and has same size + if (existingBatchIndex != -1) { + continue + } + + // Case 3: Create new batch + val items = generateUiItems(key, data) + batches.add(Batch(key = key, data = items)) + } + + return batches + } + + private fun generateUiItems(key: Int, data: PaginationWrapper): List { + val items = mutableListOf() + + // Add title for the first batch + if (key == 0) { + items.add(TxHistoryUM.TxHistoryItemUM.Title(onExploreClick = txHistoryUiActions::openExplorer)) + } + + // Process batch items only if there are any + if (data.items.isNotEmpty()) { + // Add first item with its group title + val firstItem = data.items.first() + val firstDate = firstItem.timestampInMillis.toDateFormatWithTodayYesterday() + + items.add( + TxHistoryUM.TxHistoryItemUM.GroupTitle( + title = firstDate, + itemKey = UUID.randomUUID().toString(), + ), + ) + items.add(TxHistoryUM.TxHistoryItemUM.Transaction(txHistoryItemConverter.convert(firstItem))) + + // Process remaining items with date separators when needed + data.items.zipWithNext { current, next -> + val currentDate = current.timestampInMillis.toDateFormatWithTodayYesterday() + val nextDate = next.timestampInMillis.toDateFormatWithTodayYesterday() + + if (currentDate != nextDate) { + items.add( + TxHistoryUM.TxHistoryItemUM.GroupTitle( + title = nextDate, + itemKey = UUID.randomUUID().toString(), + ), + ) + } + items.add(TxHistoryUM.TxHistoryItemUM.Transaction(txHistoryItemConverter.convert(next))) + } + } + + return items + } + + private fun List.transactionItemsSizeNotEqual( + txHistoryItems: List, + ): Boolean { + return this.filterIsInstance().size != txHistoryItems.size + } +} \ No newline at end of file diff --git a/features/wallet-settings/impl/build.gradle.kts b/features/wallet-settings/impl/build.gradle.kts index 9bad883a97..ca5f65ffc4 100644 --- a/features/wallet-settings/impl/build.gradle.kts +++ b/features/wallet-settings/impl/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { /* Project - API */ implementation(projects.features.walletSettings.api) implementation(projects.features.manageTokens.api) + implementation(projects.features.nft.api) /* Project - Core */ implementation(projects.core.decompose) diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/impl/DefaultWalletSettingsComponent.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/impl/DefaultWalletSettingsComponent.kt index 39ed1d65a5..1f4adf5af6 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/impl/DefaultWalletSettingsComponent.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/impl/DefaultWalletSettingsComponent.kt @@ -5,7 +5,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.slot.childSlot import com.arkivanov.decompose.router.slot.dismiss import com.tangem.core.decompose.context.AppComponentContext diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/impl/model/RenameWalletModel.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/impl/model/RenameWalletModel.kt index 2579a58329..ea34279a52 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/impl/model/RenameWalletModel.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/impl/model/RenameWalletModel.kt @@ -1,7 +1,7 @@ package com.tangem.feature.walletsettings.component.impl.model import androidx.compose.ui.text.input.TextFieldValue -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.ui.UiMessageSender @@ -36,7 +36,7 @@ import javax.inject.Inject * [REDACTED_AUTHOR] */ -@ComponentScoped +@ModelScoped internal class RenameWalletModel @Inject constructor( paramsContainer: ParamsContainer, getWalletNamesUseCase: GetWalletNamesUseCase, diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt index 47d910bafc..4e77769e0d 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt @@ -23,6 +23,9 @@ internal class PreviewWalletSettingsComponent : WalletSettingsComponent { isReferralAvailable = true, isLinkMoreCardsAvailable = true, isRenameWalletAvailable = false, + isNFTFeatureEnabled = true, + isNFTEnabled = true, + onCheckedNFTChange = {}, renameWallet = {}, forgetWallet = {}, onLinkMoreCardsClick = {}, diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/di/WalletSettingsModelModule.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/di/WalletSettingsModelModule.kt index 6cc2cd67eb..195e71a172 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/di/WalletSettingsModelModule.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/di/WalletSettingsModelModule.kt @@ -1,6 +1,6 @@ package com.tangem.feature.walletsettings.di -import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.feature.walletsettings.component.impl.model.RenameWalletModel import com.tangem.feature.walletsettings.model.WalletSettingsModel @@ -11,7 +11,7 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap @Module -@InstallIn(DecomposeComponent::class) +@InstallIn(ModelComponent::class) internal interface WalletSettingsModelModule { @Binds diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/entity/WalletSettingsItemUM.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/entity/WalletSettingsItemUM.kt index ee17e4637b..3e2eb34bad 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/entity/WalletSettingsItemUM.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/entity/WalletSettingsItemUM.kt @@ -16,6 +16,13 @@ internal sealed class WalletSettingsItemUM { val blocks: ImmutableList, ) : WalletSettingsItemUM() + data class WithSwitch( + override val id: String, + val title: TextReference, + val isChecked: Boolean, + val onCheckedChange: (Boolean) -> Unit, + ) : WalletSettingsItemUM() + data class WithText( override val id: String, val title: TextReference, diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt index 7952d3d3a1..764f57f277 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt @@ -6,7 +6,7 @@ import com.arkivanov.decompose.router.slot.activate import com.tangem.common.routing.AppRoute import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.utils.AnalyticsContextProxy -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router @@ -21,6 +21,7 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.redux.LegacyAction import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.DeleteWalletUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsSyncUseCase @@ -31,6 +32,7 @@ import com.tangem.feature.walletsettings.entity.WalletSettingsItemUM import com.tangem.feature.walletsettings.entity.WalletSettingsUM import com.tangem.feature.walletsettings.impl.R import com.tangem.feature.walletsettings.utils.ItemsBuilder +import com.tangem.features.nft.NFTFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf @@ -40,7 +42,7 @@ import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList") -@ComponentScoped +@ModelScoped internal class WalletSettingsModel @Inject constructor( getWalletUseCase: GetUserWalletUseCase, paramsContainer: ParamsContainer, @@ -53,6 +55,8 @@ internal class WalletSettingsModel @Inject constructor( private val analyticsContextProxy: AnalyticsContextProxy, private val reduxStateHolder: ReduxStateHolder, private val getShouldSaveUserWalletsSyncUseCase: ShouldSaveUserWalletsSyncUseCase, + private val walletsRepository: WalletsRepository, + private val nftFeatureToggles: NFTFeatureToggles, ) : Model() { val params: WalletSettingsComponent.Params = paramsContainer.require() @@ -68,11 +72,19 @@ internal class WalletSettingsModel @Inject constructor( init { getWalletUseCase.invokeFlow(params.userWalletId) .distinctUntilChanged() - .onEach { maybeWallet -> - val wallet = maybeWallet.getOrNull() ?: return@onEach + .combine(walletsRepository.nftEnabledStatus(params.userWalletId)) { maybeWallet, nftEnabled -> + val wallet = maybeWallet.getOrNull() ?: return@combine val isRenameWalletAvailable = getShouldSaveUserWalletsSyncUseCase() state.update { value -> - value.copy(items = buildItems(wallet, dialogNavigation, isRenameWalletAvailable)) + value.copy( + items = buildItems( + userWallet = wallet, + dialogNavigation = dialogNavigation, + isRenameWalletAvailable = isRenameWalletAvailable, + isNFTFeatureEnabled = nftFeatureToggles.isNFTEnabled, + isNFTEnabled = nftEnabled, + ), + ) } } .launchIn(modelScope) @@ -82,6 +94,8 @@ internal class WalletSettingsModel @Inject constructor( userWallet: UserWallet, dialogNavigation: SlotNavigation, isRenameWalletAvailable: Boolean, + isNFTFeatureEnabled: Boolean, + isNFTEnabled: Boolean, ): PersistentList = itemsBuilder.buildItems( userWalletId = userWallet.walletId, userWalletName = userWallet.name, @@ -90,6 +104,9 @@ internal class WalletSettingsModel @Inject constructor( isManageTokensAvailable = userWallet.isMultiCurrency, isRenameWalletAvailable = isRenameWalletAvailable, renameWallet = { openRenameWalletDialog(userWallet, dialogNavigation) }, + isNFTFeatureEnabled = isNFTFeatureEnabled, + isNFTEnabled = isNFTEnabled, + onCheckedNFTChange = ::onCheckedNFTChange, forgetWallet = { val message = DialogMessage( message = resourceReference(R.string.user_wallet_list_delete_prompt), @@ -151,4 +168,14 @@ internal class WalletSettingsModel @Inject constructor( router.push(AppRoute.OnboardingWallet()) } + + private fun onCheckedNFTChange(isChecked: Boolean) { + modelScope.launch { + if (isChecked) { + walletsRepository.enableNFT(params.userWalletId) + } else { + walletsRepository.disableNFT(params.userWalletId) + } + } + } } \ No newline at end of file diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/WalletSettingsScreen.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/WalletSettingsScreen.kt index b2c0270b20..675e86d521 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/WalletSettingsScreen.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/WalletSettingsScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.TangemSwitch import com.tangem.core.ui.components.appbar.TangemTopAppBar import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM import com.tangem.core.ui.components.block.BlockCard @@ -96,6 +97,10 @@ private fun Content(state: WalletSettingsUM, modifier: Modifier = Modifier) { modifier = itemModifier, model = item, ) + is WalletSettingsItemUM.WithSwitch -> SwitchBlock( + modifier = itemModifier, + model = item, + ) } } } @@ -165,6 +170,34 @@ private fun TextBlock(model: WalletSettingsItemUM.WithText, modifier: Modifier = } } +@Composable +private fun SwitchBlock(model: WalletSettingsItemUM.WithSwitch, modifier: Modifier = Modifier) { + BlockCard( + modifier = modifier.fillMaxWidth(), + enabled = model.isChecked, + ) { + Row( + modifier = Modifier.padding(all = TangemTheme.dimens.spacing12), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12, Alignment.Start), + ) { + Text( + modifier = Modifier.weight(1f), + text = model.title.resolveReference(), + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + + TangemSwitch( + checked = model.isChecked, + onCheckedChange = model.onCheckedChange, + ) + } + } +} + // region Preview @Composable @Preview(showBackground = true, widthDp = 360) diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt index c6b465997b..75fbbc0e4b 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt @@ -3,7 +3,7 @@ package com.tangem.feature.walletsettings.utils import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRoute.ManageTokens.Source import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.components.block.model.BlockUM import com.tangem.core.ui.extensions.resourceReference @@ -17,7 +17,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import javax.inject.Inject -@ComponentScoped +@ModelScoped internal class ItemsBuilder @Inject constructor( private val router: Router, private val analyticsEventHandler: AnalyticsEventHandler, @@ -31,20 +31,31 @@ internal class ItemsBuilder @Inject constructor( isReferralAvailable: Boolean, isManageTokensAvailable: Boolean, isRenameWalletAvailable: Boolean, + isNFTFeatureEnabled: Boolean, + isNFTEnabled: Boolean, + onCheckedNFTChange: (Boolean) -> Unit, forgetWallet: () -> Unit, renameWallet: () -> Unit, onLinkMoreCardsClick: () -> Unit, - ): PersistentList = persistentListOf( - buildNameItem(userWalletName, isRenameWalletAvailable, renameWallet), - buildCardItem( - userWalletId = userWalletId, - isLinkMoreCardsAvailable = isLinkMoreCardsAvailable, - isReferralAvailable = isReferralAvailable, - isManageTokensAvailable = isManageTokensAvailable, - onLinkMoreCardsClick = onLinkMoreCardsClick, - ), - buildForgetItem(forgetWallet), - ) + ): PersistentList = persistentListOf() + .add(buildNameItem(userWalletName, isRenameWalletAvailable, renameWallet)) + .run { + if (isNFTFeatureEnabled) { + add(buildNFTItem(isNFTEnabled, onCheckedNFTChange)) + } else { + this + } + } + .add( + buildCardItem( + userWalletId = userWalletId, + isLinkMoreCardsAvailable = isLinkMoreCardsAvailable, + isReferralAvailable = isReferralAvailable, + isManageTokensAvailable = isManageTokensAvailable, + onLinkMoreCardsClick = onLinkMoreCardsClick, + ), + ) + .add(buildForgetItem(forgetWallet)) private fun buildNameItem(walletName: String, isRenameWalletAvailable: Boolean, renameWallet: () -> Unit) = WalletSettingsItemUM.WithText( @@ -55,6 +66,14 @@ internal class ItemsBuilder @Inject constructor( onClick = renameWallet, ) + private fun buildNFTItem(isNFTEnabled: Boolean, onCheckedNFTChange: (Boolean) -> Unit) = + WalletSettingsItemUM.WithSwitch( + id = "nft", + title = resourceReference(id = R.string.details_nft_title), + isChecked = isNFTEnabled, + onCheckedChange = onCheckedNFTChange, + ) + private fun buildCardItem( userWalletId: UserWalletId, isLinkMoreCardsAvailable: Boolean, diff --git a/features/wallet/api/build.gradle.kts b/features/wallet/api/build.gradle.kts index 2e8f294e46..8e523e4ddd 100644 --- a/features/wallet/api/build.gradle.kts +++ b/features/wallet/api/build.gradle.kts @@ -11,4 +11,8 @@ android { dependencies { /** AndroidX */ implementation(deps.androidx.fragment.ktx) + + /** Core */ + implementation(projects.core.ui) + implementation(projects.core.decompose) } \ No newline at end of file diff --git a/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/WalletEntryComponent.kt b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/WalletEntryComponent.kt new file mode 100644 index 0000000000..51b0a3f29e --- /dev/null +++ b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/WalletEntryComponent.kt @@ -0,0 +1,9 @@ +package com.tangem.features.wallet + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent + +interface WalletEntryComponent : ComposableContentComponent { + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/navigation/WalletRouter.kt b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/navigation/WalletRouter.kt deleted file mode 100644 index 168bb9eca5..0000000000 --- a/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/navigation/WalletRouter.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.features.wallet.navigation - -import androidx.fragment.app.Fragment - -/** - * Wallet feature router - * -[REDACTED_AUTHOR] - */ -interface WalletRouter { - - /** Get feature entry point [Fragment] */ - fun getEntryFragment(): Fragment -} \ No newline at end of file diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 99809111b3..d36dc2c763 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -24,8 +24,6 @@ dependencies { implementation(deps.compose.foundation) implementation(deps.compose.material) implementation(deps.compose.material3) - implementation(deps.compose.navigation) - implementation(deps.compose.navigation.hilt) implementation(deps.compose.paging) implementation(deps.compose.reorderable) implementation(deps.compose.shimmer) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/DefaultWalletEntryComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/DefaultWalletEntryComponent.kt new file mode 100644 index 0000000000..d4555ccf75 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/DefaultWalletEntryComponent.kt @@ -0,0 +1,66 @@ +package com.tangem.feature.wallet + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.arkivanov.decompose.extensions.compose.stack.Children +import com.arkivanov.decompose.extensions.compose.stack.animation.fade +import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation +import com.arkivanov.decompose.router.stack.ChildStack +import com.arkivanov.decompose.router.stack.StackNavigation +import com.arkivanov.decompose.router.stack.childStack +import com.arkivanov.decompose.router.stack.pop +import com.arkivanov.decompose.router.stack.pushNew +import com.arkivanov.decompose.value.Value +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.childByContext +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.feature.wallet.child.organizetokens.OrganizeTokensComponent +import com.tangem.feature.wallet.child.wallet.WalletComponent +import com.tangem.feature.wallet.navigation.WalletRoute +import com.tangem.features.wallet.WalletEntryComponent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultWalletEntryComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: Unit, + walletComponentFactory: WalletComponent.Factory, +) : WalletEntryComponent, AppComponentContext by appComponentContext { + + private val navigation = StackNavigation() + + private val stack: Value> = childStack( + source = navigation, + serializer = WalletRoute.serializer(), + initialConfiguration = WalletRoute.Wallet, + childFactory = { route, context -> + when (route) { + WalletRoute.Wallet -> walletComponentFactory.create( + appComponentContext = childByContext(context), + navigate = { navigation.pushNew(it) }, + ) + is WalletRoute.OrganizeTokens -> OrganizeTokensComponent( + appComponentContext = childByContext(context), + params = OrganizeTokensComponent.Params(route.userWalletId), + onBack = { navigation.pop() }, + ) + } + }, + ) + + @Composable + override fun Content(modifier: Modifier) { + Children( + stack = stack, + animation = stackAnimation(fade()), + ) { + it.instance.Content(modifier) + } + } + + @AssistedFactory + interface Factory : WalletEntryComponent.Factory { + override fun create(context: AppComponentContext, params: Unit): DefaultWalletEntryComponent + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/organizetokens/OrganizeTokensComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/organizetokens/OrganizeTokensComponent.kt new file mode 100644 index 0000000000..2763610e1f --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/organizetokens/OrganizeTokensComponent.kt @@ -0,0 +1,39 @@ +package com.tangem.feature.wallet.child.organizetokens + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.wallet.child.organizetokens.model.OrganizeTokensModel +import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScreen +import kotlinx.coroutines.launch + +internal class OrganizeTokensComponent( + appComponentContext: AppComponentContext, + params: Params, + onBack: () -> Unit, +) : ComposableContentComponent, AppComponentContext by appComponentContext { + + private val model: OrganizeTokensModel = getOrCreateModel(params) + + init { + componentScope.launch { + model.onBack.collect { onBack() } + } + } + + data class Params(val userWalletId: UserWalletId) + + @Composable + override fun Content(modifier: Modifier) { + val uiState by model.uiState.collectAsStateWithLifecycle() + + OrganizeTokensScreen( + state = uiState, + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/organizetokens/model/OrganizeTokensModel.kt similarity index 85% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/organizetokens/model/OrganizeTokensModel.kt index a27f15bdab..9d064cf565 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/organizetokens/model/OrganizeTokensModel.kt @@ -1,9 +1,12 @@ -package com.tangem.feature.wallet.presentation.organizetokens +package com.tangem.feature.wallet.child.organizetokens.model -import androidx.lifecycle.* +import androidx.compose.runtime.Stable import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase @@ -13,24 +16,27 @@ import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.ToggleTokenListGroupingUseCase import com.tangem.domain.tokens.ToggleTokenListSortingUseCase import com.tangem.domain.tokens.model.TokenList -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.wallet.child.organizetokens.OrganizeTokensComponent +import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensIntents +import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder import com.tangem.feature.wallet.presentation.organizetokens.analytics.PortfolioOrganizeTokensAnalyticsEvent import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState import com.tangem.feature.wallet.presentation.organizetokens.utils.CryptoCurrenciesIdsResolver import com.tangem.feature.wallet.presentation.organizetokens.utils.common.disableSortingByBalance import com.tangem.feature.wallet.presentation.organizetokens.utils.dnd.DragAndDropAdapter -import com.tangem.feature.wallet.presentation.router.InnerWalletRouter -import com.tangem.feature.wallet.presentation.router.WalletRoute import com.tangem.utils.Provider -import dagger.hilt.android.lifecycle.HiltViewModel +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject @Suppress("LongParameterList") -@HiltViewModel -internal class OrganizeTokensViewModel @Inject constructor( +@Stable +@ModelScoped +internal class OrganizeTokensModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, private val getTokenListUseCase: GetTokenListUseCase, private val toggleTokenListGroupingUseCase: ToggleTokenListGroupingUseCase, private val toggleTokenListSortingUseCase: ToggleTokenListSortingUseCase, @@ -38,10 +44,7 @@ internal class OrganizeTokensViewModel @Inject constructor( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val analyticsEventsHandler: AnalyticsEventHandler, - savedStateHandle: SavedStateHandle, -) : ViewModel(), DefaultLifecycleObserver, OrganizeTokensIntents { - - lateinit var router: InnerWalletRouter +) : Model(), OrganizeTokensIntents { private val selectedAppCurrencyFlow = createSelectedAppCurrencyFlow() @@ -57,33 +60,30 @@ internal class OrganizeTokensViewModel @Inject constructor( appCurrencyProvider = Provider(selectedAppCurrencyFlow::value), ) - private val userWalletId: UserWalletId by lazy { - val userWalletIdValue: String = checkNotNull(savedStateHandle[WalletRoute.userWalletIdKey]) - - UserWalletId(userWalletIdValue) - } + private val userWalletId = paramsContainer.require().userWalletId private var cachedTokenList: TokenList? = null val uiState: StateFlow = stateHolder.stateFlow - override fun onCreate(owner: LifecycleOwner) { + val onBack = MutableSharedFlow() + + init { analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.ScreenOpened) getBalanceHidingSettingsUseCase() - .flowWithLifecycle(owner.lifecycle) .onEach { isBalanceHidden = it.isBalanceHidden stateHolder.updateHiddenState(isBalanceHidden) } - .launchIn(viewModelScope) + .launchIn(modelScope) bootstrapTokenList() bootstrapDragAndDropUpdates() } override fun onBackClick() { - router.popBackStack() + modelScope.launch { onBack.emit(Unit) } } override fun onSortClick() { @@ -92,7 +92,7 @@ internal class OrganizeTokensViewModel @Inject constructor( analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.ByBalance) - viewModelScope.launch { + modelScope.launch { toggleTokenListSortingUseCase(list).fold( ifLeft = stateHolder::updateStateWithError, ifRight = { @@ -108,7 +108,7 @@ internal class OrganizeTokensViewModel @Inject constructor( analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.Group) - viewModelScope.launch { + modelScope.launch { toggleTokenListGroupingUseCase(list).fold( ifLeft = stateHolder::updateStateWithError, ifRight = { @@ -120,7 +120,7 @@ internal class OrganizeTokensViewModel @Inject constructor( } override fun onApplyClick() { - viewModelScope.launch { + modelScope.launch { stateHolder.updateStateToDisplayProgress() val listState = uiState.value.itemsState @@ -144,7 +144,7 @@ internal class OrganizeTokensViewModel @Inject constructor( result.fold( ifLeft = stateHolder::updateStateWithError, ifRight = { - router.popBackStack() + modelScope.launch { onBack.emit(Unit) } stateHolder.updateStateToHideProgress() }, ) @@ -154,11 +154,11 @@ internal class OrganizeTokensViewModel @Inject constructor( override fun onCancelClick() { analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.Cancel) - router.popBackStack() + modelScope.launch { onBack.emit(Unit) } } private fun bootstrapTokenList() { - viewModelScope.launch { + modelScope.launch { val tokenList = getTokenList() ?: return@launch stateHolder.updateStateWithTokenList(tokenList) @@ -192,7 +192,7 @@ internal class OrganizeTokensViewModel @Inject constructor( stateHolder.updateStateWithManualSorting(updatedListState) } - .launchIn(viewModelScope) + .launchIn(modelScope) } private fun disableSortingByBalanceIfListChanged(dragOperationType: DragAndDropAdapter.DragOperation.Type) { @@ -210,7 +210,7 @@ internal class OrganizeTokensViewModel @Inject constructor( maybeAppCurrency.getOrElse { AppCurrency.Default } } .stateIn( - scope = viewModelScope, + scope = modelScope, started = SharingStarted.Eagerly, initialValue = AppCurrency.Default, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt new file mode 100644 index 0000000000..a0bad13d8b --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt @@ -0,0 +1,84 @@ +package com.tangem.feature.wallet.child.wallet + +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.arkivanov.decompose.router.slot.childSlot +import com.arkivanov.decompose.router.slot.dismiss +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.child +import com.tangem.core.decompose.context.childByContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.core.ui.utils.findActivity +import com.tangem.feature.wallet.child.wallet.model.WalletModel +import com.tangem.feature.wallet.navigation.WalletRoute +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig +import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen +import com.tangem.feature.walletsettings.component.RenameWalletComponent +import com.tangem.features.markets.entry.MarketsEntryComponent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.launch + +internal class WalletComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted navigate: (WalletRoute) -> Unit, + private val renameWalletComponentFactory: RenameWalletComponent.Factory, + private val marketsEntryComponentFactory: MarketsEntryComponent.Factory, +) : ComposableContentComponent, AppComponentContext by appComponentContext { + + private val model: WalletModel = getOrCreateModel() + + init { + lifecycle.subscribe(model.screenLifecycleProvider) + componentScope.launch { model.innerWalletRouter.navigateToFlow.collect { navigate(it) } } + } + + private val dialog = childSlot( + source = model.innerWalletRouter.dialogNavigation, + serializer = WalletDialogConfig.serializer(), + handleBackButton = true, + childFactory = { dialogConfig, componentContext -> + when (dialogConfig) { + is WalletDialogConfig.RenameWallet -> { + renameWalletComponentFactory.create( + context = childByContext(componentContext), + params = RenameWalletComponent.Params( + userWalletId = dialogConfig.userWalletId, + currentName = dialogConfig.currentName, + onDismiss = model.innerWalletRouter.dialogNavigation::dismiss, + ), + ) + } + } + }, + ) + + private val marketsEntryComponent = marketsEntryComponentFactory.create(child("marketsEntryComponent")) + + @Composable + override fun Content(modifier: Modifier) { + val activity = LocalContext.current.findActivity() + BackHandler { activity.finish() } + + val dialog by dialog.subscribeAsState() + + WalletScreen( + state = model.uiState.collectAsStateWithLifecycle().value, + marketsEntryComponent = marketsEntryComponent, + ) + + dialog.child?.instance?.Dialog() + } + + @AssistedFactory + interface Factory { + fun create(appComponentContext: AppComponentContext, navigate: (WalletRoute) -> Unit): WalletComponent + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt similarity index 89% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt index cff354e8fa..044aaec8c6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt @@ -1,17 +1,17 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels +package com.tangem.feature.wallet.child.wallet.model import androidx.compose.runtime.Stable -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.settings.* import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.domain.wallets.usecase.GetWalletsUseCase +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.deeplink.WalletDeepLinksHandler import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent @@ -29,13 +29,11 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenSta import com.tangem.feature.wallet.presentation.wallet.state.transformers.* import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import com.tangem.utils.Provider import com.tangem.utils.coroutines.* -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -45,8 +43,9 @@ import javax.inject.Inject @Suppress("LongParameterList", "LargeClass") @Stable -@HiltViewModel -internal class WalletViewModel @Inject constructor( +@ModelScoped +internal class WalletModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, private val stateHolder: WalletStateController, private val clickIntents: WalletClickIntents, private val walletEventSender: WalletEventSender, @@ -59,8 +58,6 @@ internal class WalletViewModel @Inject constructor( private val canUseBiometryUseCase: CanUseBiometryUseCase, private val isWalletsScrollPreviewEnabled: IsWalletsScrollPreviewEnabled, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, - private val dispatchers: CoroutineDispatcherProvider, - private val screenLifecycleProvider: ScreenLifecycleProvider, private val selectedWalletAnalyticsSender: SelectedWalletAnalyticsSender, private val walletDeepLinksHandler: WalletDeepLinksHandler, private val walletNameMigrationUseCase: WalletNameMigrationUseCase, @@ -70,12 +67,13 @@ internal class WalletViewModel @Inject constructor( private val tokenListStore: MultiWalletTokenListStore, private val onrampStatusFactory: OnrampStatusFactory, private val walletFeatureToggles: WalletFeatureToggles, + val screenLifecycleProvider: ScreenLifecycleProvider, + val innerWalletRouter: InnerWalletRouter, analyticsEventsHandler: AnalyticsEventHandler, -) : ViewModel() { +) : Model() { val uiState: StateFlow = stateHolder.uiState - private lateinit var router: InnerWalletRouter private val walletsUpdateJobHolder = JobHolder() private val refreshWalletJobHolder = JobHolder() private val expressStatusJobHolder = JobHolder() @@ -96,25 +94,18 @@ internal class WalletViewModel @Inject constructor( subscribeToScreenBackgroundState() subscribeOnPushNotificationsPermission() subscribeOnExpressTransactionsUpdates() + + clickIntents.initialize(innerWalletRouter, modelScope) } private fun maybeMigrateNames() { - viewModelScope.launch { + modelScope.launch { walletNameMigrationUseCase() } } - fun setWalletRouter(router: InnerWalletRouter) { - this.router = router - clickIntents.initialize(router, viewModelScope) - } - - fun subscribeToLifecycle(lifecycleOwner: LifecycleOwner) { - lifecycleOwner.lifecycle.addObserver(screenLifecycleProvider) - } - - override fun onCleared() { - super.onCleared() + override fun onDestroy() { + super.onDestroy() tokenListStore.clear() stateHolder.clear() @@ -122,15 +113,15 @@ internal class WalletViewModel @Inject constructor( } private fun suggestToEnableBiometrics() { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { withContext(dispatchers.io) { delay(timeMillis = 1_800) } - if (isShowSaveWalletScreenEnabled()) router.openSaveUserWalletScreen() + if (isShowSaveWalletScreenEnabled()) innerWalletRouter.openSaveUserWalletScreen() } } private fun suggestToOpenMarkets() { - viewModelScope.launch { + modelScope.launch { withContext(dispatchers.io) { delay(timeMillis = 1_800) } if (shouldShowMarketsTooltipUseCase()) { @@ -144,7 +135,7 @@ internal class WalletViewModel @Inject constructor( } private suspend fun isShowSaveWalletScreenEnabled(): Boolean { - return router.isWalletLastScreen() && shouldShowSaveWalletScreenUseCase() && canUseBiometryUseCase() + return innerWalletRouter.isWalletLastScreen() && shouldShowSaveWalletScreenUseCase() && canUseBiometryUseCase() } private fun subscribeToUserWalletsUpdates() { @@ -156,7 +147,7 @@ internal class WalletViewModel @Inject constructor( } .onEach(::updateWallets) .flowOn(dispatchers.main) - .launchIn(viewModelScope) + .launchIn(modelScope) .saveIn(walletsUpdateJobHolder) } @@ -168,11 +159,11 @@ internal class WalletViewModel @Inject constructor( stateHolder.update(transformer = UpdateBalanceHidingModeTransformer(it.isBalanceHidden)) } .flowOn(dispatchers.main) - .launchIn(viewModelScope) + .launchIn(modelScope) } private fun subscribeOnPushNotificationsPermission() { - viewModelScope.launch { + modelScope.launch { val shouldRequestPush = shouldAskPermissionUseCase(PUSH_PERMISSION) val isPushPermissionAvailable = getPushPermissionOrNull() != null if (!shouldRequestPush || !isPushPermissionAvailable) return@launch @@ -203,10 +194,10 @@ internal class WalletViewModel @Inject constructor( selectedWalletAnalyticsSender.send(selectedWallet) } - walletDeepLinksHandler.registerForWallet(viewModel = this, userWallet = selectedWallet) + walletDeepLinksHandler.registerForWallet(scope = modelScope, userWallet = selectedWallet) } .flowOn(dispatchers.main) - .launchIn(viewModelScope) + .launchIn(modelScope) } } @@ -227,14 +218,14 @@ internal class WalletViewModel @Inject constructor( !isBackground -> subscribeOnExpressTransactionsUpdates() } } - .launchIn(viewModelScope) + .launchIn(modelScope) } private fun subscribeOnExpressTransactionsUpdates() { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { expressTxStatusTaskScheduler.cancelTask() expressTxStatusTaskScheduler.scheduleTask( - viewModelScope, + modelScope, PeriodicTask( isDelayFirst = false, delay = EXPRESS_STATUS_UPDATE_DELAY, @@ -249,7 +240,7 @@ internal class WalletViewModel @Inject constructor( } private fun needToRefreshTimer() { - viewModelScope.launch { + modelScope.launch { delay(REFRESH_WALLET_BACKGROUND_TIMER_MILLIS) needToRefreshWallet = true }.saveIn(refreshWalletJobHolder) @@ -259,7 +250,7 @@ internal class WalletViewModel @Inject constructor( needToRefreshWallet = false val state = stateHolder.uiState.value val wallet = state.wallets.getOrNull(state.selectedWalletIndex) ?: return - viewModelScope.launch { + modelScope.launch { refreshMultiCurrencyWalletQuotesUseCase(wallet.walletCardState.id).getOrElse { Timber.e("Failed to refreshMultiCurrencyWalletQuotesUseCase $it") } @@ -279,7 +270,7 @@ internal class WalletViewModel @Inject constructor( userWallet = action.selectedWallet, clickIntents = clickIntents, isRefresh = true, - coroutineScope = viewModelScope, + coroutineScope = modelScope, ) stateHolder.update( @@ -299,12 +290,6 @@ internal class WalletViewModel @Inject constructor( } private suspend fun initializeWallets(action: WalletsUpdateActionResolver.Action.InitializeWallets) { - walletScreenContentLoader.load( - userWallet = action.selectedWallet, - clickIntents = clickIntents, - coroutineScope = viewModelScope, - ) - stateHolder.update( transformer = InitializeWalletsTransformer( selectedWalletIndex = action.selectedWalletIndex, @@ -315,6 +300,12 @@ internal class WalletViewModel @Inject constructor( ), ) + walletScreenContentLoader.load( + userWallet = action.selectedWallet, + clickIntents = clickIntents, + coroutineScope = modelScope, + ) + if (action.wallets.size > 1 && isWalletsScrollPreviewEnabled()) { withContext(dispatchers.io) { delay(timeMillis = 1_800) } @@ -337,7 +328,7 @@ internal class WalletViewModel @Inject constructor( walletScreenContentLoader.load( userWallet = action.selectedWallet, clickIntents = clickIntents, - coroutineScope = viewModelScope, + coroutineScope = modelScope, ) stateHolder.update( @@ -355,7 +346,7 @@ internal class WalletViewModel @Inject constructor( walletScreenContentLoader.load( userWallet = action.selectedWallet, clickIntents = clickIntents, - coroutineScope = viewModelScope, + coroutineScope = modelScope, ) stateHolder.update( @@ -381,7 +372,7 @@ internal class WalletViewModel @Inject constructor( walletScreenContentLoader.load( userWallet = action.selectedWallet, clickIntents = clickIntents, - coroutineScope = viewModelScope, + coroutineScope = modelScope, ) val newSelectedWalletIndex = if (action.selectedWalletIndex - action.deletedWalletIndex == 1) { @@ -439,7 +430,7 @@ internal class WalletViewModel @Inject constructor( walletScreenContentLoader.load( userWallet = action.selectedWallet, clickIntents = clickIntents, - coroutineScope = viewModelScope, + coroutineScope = modelScope, ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletsUpdateActionResolver.kt similarity index 98% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletsUpdateActionResolver.kt index 97073d4a82..5594553aab 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletsUpdateActionResolver.kt @@ -1,6 +1,7 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels +package com.tangem.feature.wallet.child.wallet.model import arrow.core.getOrElse +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.common.util.getCardsCount import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId @@ -9,7 +10,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState -import dagger.hilt.android.scopes.ViewModelScoped import timber.log.Timber import javax.inject.Inject @@ -18,7 +18,7 @@ import javax.inject.Inject * * @property getSelectedWalletSyncUseCase use case that returns selected wallet */ -@ViewModelScoped +@ModelScoped internal class WalletsUpdateActionResolver @Inject constructor( private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, ) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/BaseWalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/BaseWalletClickIntents.kt similarity index 70% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/BaseWalletClickIntents.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/BaseWalletClickIntents.kt index d9be6984f5..d4e874fc9a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/BaseWalletClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/BaseWalletClickIntents.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents +package com.tangem.feature.wallet.child.wallet.model.intents import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import kotlinx.coroutines.CoroutineScope @@ -14,13 +14,13 @@ import kotlin.properties.Delegates internal abstract class BaseWalletClickIntents { protected val router: InnerWalletRouter get() = _router - protected val viewModelScope: CoroutineScope get() = _viewModelScope + protected val modelScope: CoroutineScope get() = _modelScope private var _router: InnerWalletRouter by Delegates.notNull() - private var _viewModelScope: CoroutineScope by Delegates.notNull() + private var _modelScope: CoroutineScope by Delegates.notNull() open fun initialize(router: InnerWalletRouter, coroutineScope: CoroutineScope) { _router = router - _viewModelScope = coroutineScope + _modelScope = coroutineScope } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/VisaWalletIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/VisaWalletIntents.kt similarity index 94% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/VisaWalletIntents.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/VisaWalletIntents.kt index f93d82366d..4a0281d436 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/VisaWalletIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/VisaWalletIntents.kt @@ -1,6 +1,7 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents +package com.tangem.feature.wallet.child.wallet.model.intents import arrow.core.getOrElse +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModels @@ -15,7 +16,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.converte import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.VisaTxDetailsBottomSheetConverter import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.launch import timber.log.Timber @@ -32,7 +32,7 @@ internal interface VisaWalletIntents { fun onExploreClick(exploreUrl: String) } -@ViewModelScoped +@ModelScoped internal class VisaWalletIntentsImplementor @Inject constructor( private val stateController: WalletStateController, private val eventSender: WalletEventSender, @@ -49,7 +49,7 @@ internal class VisaWalletIntentsImplementor @Inject constructor( override fun onDepositClick() { val userWalletId = stateController.getSelectedWalletId() - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { val currencyStatus = getPrimaryCurrencyStatus(userWalletId) ?: return@launch createReceiveBottomSheetContent(currencyStatus)?.let { content -> @@ -79,7 +79,7 @@ internal class VisaWalletIntentsImplementor @Inject constructor( } override fun onBalancesAndLimitsClick() { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { val userWalletId = stateController.getSelectedWalletId() val balancesAndLimits = getVisaCurrencyUseCase(userWalletId) .getOrElse { @@ -104,7 +104,7 @@ internal class VisaWalletIntentsImplementor @Inject constructor( } override fun onVisaTransactionClick(id: String) { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { val userWalletId = stateController.getSelectedWalletId() val visaCurrency = getVisaCurrencyUseCase(userWalletId) .getOrElse { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCardClickIntents.kt similarity index 96% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCardClickIntents.kt index 622afd1151..fcabcee5ad 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCardClickIntents.kt @@ -1,10 +1,11 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents +package com.tangem.feature.wallet.child.wallet.model.intents import arrow.core.getOrElse import com.arkivanov.decompose.router.slot.activate import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.card.DeleteSavedAccessCodesUseCase import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.wallets.models.UserWalletId @@ -35,6 +36,7 @@ internal interface WalletCardClickIntents { // TODO: Refactor @Suppress("LongParameterList") +@ModelScoped internal class WalletCardClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, private val tokenListStore: MultiWalletTokenListStore, @@ -74,7 +76,7 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( } override fun onDeleteAfterConfirmationClick(userWalletId: UserWalletId) { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { walletScreenContentLoader.cancel(userWalletId) tokenListStore.remove(userWalletId) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletClickIntents.kt similarity index 95% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletClickIntents.kt index 1c218be754..e84eccf060 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletClickIntents.kt @@ -1,5 +1,6 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents +package com.tangem.feature.wallet.child.wallet.model.intents +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.common.util.cardTypesResolver @@ -21,7 +22,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetRefre import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListErrorTransformer import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -29,7 +29,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @Suppress("LongParameterList") -@ViewModelScoped +@ModelScoped internal class WalletClickIntents @Inject constructor( private val walletCardClickIntentsImplementor: WalletCardClickIntentsImplementor, private val warningsClickIntentsImplementer: WalletWarningsClickIntentsImplementor, @@ -75,7 +75,7 @@ internal class WalletClickIntents @Inject constructor( return } - viewModelScope.launch { + modelScope.launch { launch { neverToShowWalletsScrollPreview() } val maybeUserWallet = selectWalletUseCase( @@ -88,7 +88,7 @@ internal class WalletClickIntents @Inject constructor( walletScreenContentLoader.load( userWallet = it, clickIntents = this@WalletClickIntents, - coroutineScope = viewModelScope, + coroutineScope = modelScope, ) } } @@ -107,6 +107,7 @@ internal class WalletClickIntents @Inject constructor( is WalletState.MultiCurrency.Locked, is WalletState.SingleCurrency.Locked, is WalletState.Visa.Locked, + is WalletState.Visa.AccessTokenLocked, -> Unit } } @@ -122,7 +123,7 @@ internal class WalletClickIntents @Inject constructor( SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = showRefreshState), ) - viewModelScope.launch { + modelScope.launch { val maybeFetchResult = if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) { fetchCardTokenListUseCase(userWalletId = userWallet.walletId, refresh = true) } else { @@ -167,14 +168,14 @@ internal class WalletClickIntents @Inject constructor( SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = showRefreshState), ) - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { fetchCurrencyStatusUseCase(userWallet.walletId, refresh = true) walletScreenContentLoader.load( userWallet = userWallet, clickIntents = this@WalletClickIntents, isRefresh = true, - coroutineScope = viewModelScope, + coroutineScope = modelScope, ) stateHolder.update( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt similarity index 91% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt index 3d00a7564b..90fdd5b760 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt @@ -1,7 +1,8 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents +package com.tangem.feature.wallet.child.wallet.model.intents import arrow.core.getOrElse import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.ShouldShowMarketsTooltipUseCase import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase @@ -16,15 +17,11 @@ import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory import com.tangem.feature.wallet.presentation.wallet.domain.unwrap import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.model.* -import com.tangem.feature.wallet.presentation.wallet.state.model.ActionsBottomSheetConfig -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.OpenBottomSheetTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCurrencyActionsConverter import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch @@ -33,8 +30,6 @@ import javax.inject.Inject internal interface WalletContentClickIntents { - fun onBackClick() - fun onDetailsClick() fun onManageTokensClick() @@ -53,7 +48,7 @@ internal interface WalletContentClickIntents { fun onDissmissBottomSheet() - fun onGoToProviderClick(externalTxId: String) + fun onGoToProviderClick(externalTxUrl: String) fun onExpressTransactionClick(txId: String) @@ -63,7 +58,7 @@ internal interface WalletContentClickIntents { } @Suppress("LongParameterList") -@ViewModelScoped +@ModelScoped internal class WalletContentClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, private val currencyActionsClickIntents: WalletCurrencyActionsClickIntentsImplementor, @@ -79,10 +74,8 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( private val walletEventSender: WalletEventSender, ) : BaseWalletClickIntents(), WalletContentClickIntents { - override fun onBackClick() = router.popBackStack() - override fun onDetailsClick() { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { val userWalletId = stateHolder.getSelectedWalletId() val userWallet = getUserWalletUseCase(userWalletId).getOrElse { Timber.e( @@ -124,7 +117,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( override fun onDismissMarketsOnboarding() { stateHolder.update { it.copy(showMarketsOnboarding = false) } - viewModelScope.launch { + modelScope.launch { shouldShowMarketsTooltipUseCase(isShown = true) } } @@ -134,7 +127,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( } override fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { val userWalletId = stateHolder.getSelectedWalletId() val userWallet = getUserWalletUseCase(userWalletId).getOrElse { Timber.e( @@ -169,7 +162,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( } override fun onTransactionClick(txHash: String) { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { val currency = getPrimaryCurrencyStatusUpdatesUseCase.unwrap( userWalletId = stateHolder.getSelectedWalletId(), ) @@ -191,7 +184,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( override fun onDissmissBottomSheet() { val userWalletId = stateHolder.getSelectedWalletId() if (stateHolder.getSelectedWallet().bottomSheetConfig?.content is ExpressStatusBottomSheetConfig) { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { onrampStatusFactory.removeTransactionOnBottomSheetClosed(forceDispose = false) } } @@ -199,7 +192,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( } override fun onExpressTransactionClick(txId: String) { - viewModelScope.launch { + modelScope.launch { val userWalletId = stateHolder.getSelectedWalletId() val singleWalletState = stateHolder.getSelectedWallet() as? WalletState.SingleCurrency.Content ?: return@launch @@ -236,7 +229,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( override fun onDisposeExpressStatus() { val userWalletId = stateHolder.getSelectedWalletId() if (stateHolder.getSelectedWallet().bottomSheetConfig?.content is ExpressStatusBottomSheetConfig) { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { onrampStatusFactory.removeTransactionOnBottomSheetClosed(forceDispose = true) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt similarity index 97% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt index b5a1671df6..d96b0968ad 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents +package com.tangem.feature.wallet.child.wallet.model.intents import com.tangem.blockchain.common.address.AddressType import com.tangem.common.routing.AppRoute @@ -7,6 +7,7 @@ import com.tangem.common.ui.tokens.getUnavailabilityReasonText import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.navigation.share.ShareManager import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent @@ -57,7 +58,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSend import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.features.swap.SwapFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull @@ -103,7 +103,7 @@ interface WalletCurrencyActionsClickIntents { } @Suppress("LongParameterList", "LargeClass") -@ViewModelScoped +@ModelScoped internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, private val walletEventSender: WalletEventSender, @@ -215,7 +215,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( event = TokenScreenAnalyticsEvent.ButtonCopyAddress(cryptoCurrencyStatus.currency.symbol), ) - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { walletManagersFacade.getDefaultAddress( userWalletId = stateHolder.getSelectedWalletId(), network = cryptoCurrencyStatus.currency.network, @@ -233,7 +233,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( event = TokenScreenAnalyticsEvent.ButtonRemoveToken(cryptoCurrencyStatus.currency.symbol), ) - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { walletEventSender.send( event = WalletEvent.ShowAlert( state = getHideTokeAlertConfig(stateHolder.getSelectedWalletId(), cryptoCurrencyStatus), @@ -280,7 +280,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( override fun onPerformHideToken(cryptoCurrencyStatus: CryptoCurrencyStatus) { val userWalletId = stateHolder.getSelectedWalletId() - viewModelScope.launch(dispatchers.io) { + modelScope.launch(dispatchers.io) { removeCurrencyUseCase(userWalletId, cryptoCurrencyStatus.currency) .fold( ifLeft = { @@ -310,7 +310,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( if (handleUnavailabilityReason(unavailabilityReason)) return showErrorIfDemoModeOrElse { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { reduxStateHolder.dispatch( action = TradeCryptoAction.Sell( cryptoCurrencyStatus = cryptoCurrencyStatus, @@ -347,7 +347,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( ) } else { showErrorIfDemoModeOrElse { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { reduxStateHolder.dispatch( TradeCryptoAction.Buy( userWallet = userWallet, @@ -390,7 +390,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( } override fun onAnalyticsClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { - viewModelScope.launch { + modelScope.launch { val rawId = cryptoCurrencyStatus.currency.id.rawCurrencyId ?: return@launch val tokenMarketParams = TokenMarketParams( @@ -423,7 +423,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWallet.walletId)) - viewModelScope.launch { + modelScope.launch { val userWalletId = stateHolder.getSelectedWalletId() val cryptoCurrency = cryptoCurrencyStatus.currency @@ -458,7 +458,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( return } - viewModelScope.launch { + modelScope.launch { val swapRoute = getSwapRoute( AppRoute.SwapCrypto(userWalletId = userWalletId), ) @@ -485,7 +485,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( private fun openExplorer() { val userWalletId = stateHolder.getSelectedWalletId() - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { val currencyStatus = getPrimaryCurrencyStatusUpdatesUseCase.unwrap(userWalletId) ?: return@launch when (val addresses = currencyStatus.value.networkAddress) { @@ -531,7 +531,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( currency: CryptoCurrency, addressModel: AddressModel, ) { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { router.openUrl( url = getExploreUrlUseCase( userWalletId = userWalletId, @@ -565,7 +565,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( private fun handleUnavailabilityReason(unavailabilityReason: ScenarioUnavailabilityReason): Boolean { if (unavailabilityReason == ScenarioUnavailabilityReason.None) return false - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { walletEventSender.send( event = WalletEvent.ShowAlert( state = WalletAlertState.DefaultAlert( @@ -585,7 +585,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( route: AppRoute, eventCreator: (AnalyticsParam.Status) -> MainScreenAnalyticsEvent, ) { - viewModelScope.launch { + modelScope.launch { statusFlow.foldStatus( onContent = { handleContent(route, eventCreator) }, onError = { handleError(eventCreator = eventCreator) }, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletPushPermissionClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletPushPermissionClickIntents.kt similarity index 89% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletPushPermissionClickIntents.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletPushPermissionClickIntents.kt index 98560342e5..6c576964f8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletPushPermissionClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletPushPermissionClickIntents.kt @@ -1,11 +1,11 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents +package com.tangem.feature.wallet.child.wallet.model.intents import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.settings.NeverRequestPermissionUseCase import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION -import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.launch import javax.inject.Inject @@ -20,7 +20,7 @@ internal interface WalletPushPermissionClickIntents { fun onAllowPushPermission() } -@ViewModelScoped +@ModelScoped internal class WalletPushPermissionClickIntentsImplementor @Inject constructor( private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase, private val analyticsEventHandler: AnalyticsEventHandler, @@ -36,7 +36,7 @@ internal class WalletPushPermissionClickIntentsImplementor @Inject constructor( override fun onNeverAskPushPermission(isUserDismissed: Boolean) { if (isUserDismissedDialog != isUserDismissed) return - viewModelScope.launch { + modelScope.launch { analyticsEventHandler.send( PushNotificationAnalyticEvents.ButtonLater(AnalyticsParam.ScreensSources.Main), ) @@ -46,14 +46,14 @@ internal class WalletPushPermissionClickIntentsImplementor @Inject constructor( override fun onDenyPushPermission() { analyticsEventHandler.send(PushNotificationAnalyticEvents.PermissionStatus(isAllowed = false)) - viewModelScope.launch { + modelScope.launch { neverRequestPermissionUseCase(PUSH_PERMISSION) } } override fun onAllowPushPermission() { analyticsEventHandler.send(PushNotificationAnalyticEvents.PermissionStatus(isAllowed = true)) - viewModelScope.launch { + modelScope.launch { neverRequestPermissionUseCase(PUSH_PERMISSION) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt similarity index 94% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt index b3dc26a447..aab061ab81 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt @@ -1,9 +1,10 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents +package com.tangem.feature.wallet.child.wallet.model.intents import arrow.core.getOrElse import com.tangem.common.TangemBlogUrlBuilder import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.card.DerivePublicKeysUseCase @@ -38,7 +39,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -55,6 +55,8 @@ internal interface WalletWarningsClickIntents { fun onUnlockWalletClick() + fun onUnlockVisaAccessClick() + fun onScanToUnlockWalletClick() fun onLikeAppClick() @@ -79,7 +81,7 @@ internal interface WalletWarningsClickIntents { } @Suppress("LongParameterList") -@ViewModelScoped +@ModelScoped internal class WalletWarningsClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, private val walletEventSender: WalletEventSender, @@ -108,7 +110,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( } private fun prepareAndStartOnboardingProcess() { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { getSelectedUserWallet()?.let { reduxStateHolder.dispatch( LegacyAction.StartOnboardingProcess( @@ -127,7 +129,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( } override fun onCloseAlreadySignedHashesWarningClick() { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { val userWallet = getSelectedUserWallet() ?: return@launch setCardWasScannedUseCase(cardId = userWallet.cardId) @@ -138,7 +140,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( analyticsEventHandler.send(Basic.CardWasScanned(AnalyticsParam.ScreensSources.Main)) analyticsEventHandler.send(MainScreen.NoticeScanYourCardTapped) - viewModelScope.launch { + modelScope.launch { val userWallet = getSelectedUserWallet() ?: return@launch derivePublicKeysUseCase( @@ -173,13 +175,17 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( override fun onUnlockWalletClick() { analyticsEventHandler.send(MainScreen.UnlockAllWithBiometrics) - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { unlockWalletsUseCase(type = UnlockType.ALL_WITHOUT_SELECT) .onRight { stateHolder.update(CloseBottomSheetTransformer(stateHolder.getSelectedWalletId())) } .onLeft(::handleUnlockWalletsError) } } + override fun onUnlockVisaAccessClick() { + openScanCardDialog() + } + private fun handleUnlockWalletsError(error: UnlockWalletsError) { val event = when (error) { is UnlockWalletsError.DataError, @@ -199,7 +205,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( } private fun openScanCardDialog() { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { scanCardToUnlockWalletClickHandler(walletId = stateHolder.getSelectedWalletId()) .onLeft { error -> when (error) { @@ -220,7 +226,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( walletEventSender.send( event = WalletEvent.RateApp( onDismissClick = { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { neverToSuggestRateAppUseCase() } }, @@ -231,7 +237,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( override fun onDislikeAppClick() { analyticsEventHandler.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Disliked)) - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { neverToSuggestRateAppUseCase() val scanResponse = getSelectedUserWallet()?.scanResponse ?: return@launch @@ -244,7 +250,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( override fun onCloseRateAppWarningClick() { analyticsEventHandler.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Closed)) - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { remindToRateAppLaterUseCase() } } @@ -257,7 +263,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Closed, ), ) - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { shouldShowSwapPromoWalletUseCase.neverToShow() } } @@ -266,14 +272,14 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( val scanResponse = getSelectedUserWallet()?.scanResponse ?: return val cardInfo = getCardInfoUseCase(scanResponse).getOrNull() ?: return - viewModelScope.launch { + modelScope.launch { sendFeedbackEmailUseCase(type = FeedbackEmailType.DirectUserRequest(cardInfo = cardInfo)) } } override fun onNoteMigrationButtonClick(url: String) { analyticsEventHandler.send(MainScreen.NotePromoButton) - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { router.openUrl(url) } } @@ -288,7 +294,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( state = WalletAlertState.SimpleOkAlert( message = resourceReference(R.string.warning_seedphrase_issue_answer_yes), onOkClick = { - viewModelScope.launch { + modelScope.launch { seedPhraseNotificationUseCase.confirm(userWalletId = userWallet.walletId) urlOpener.openUrl( @@ -311,7 +317,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( state = WalletAlertState.SimpleOkAlert( message = resourceReference(R.string.warning_seedphrase_issue_answer_no), onOkClick = { - viewModelScope.launch { + modelScope.launch { seedPhraseNotificationUseCase.decline(userWalletId = userWallet.walletId) } }, @@ -330,7 +336,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( state = WalletAlertState.SimpleOkAlert( message = resourceReference(R.string.warning_seedphrase_issue_answer_yes), onOkClick = { - viewModelScope.launch { + modelScope.launch { seedPhraseNotificationUseCase.acceptSecond(userWalletId = userWallet.walletId) urlOpener.openUrl( @@ -348,7 +354,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( analyticsEventHandler.send(MainScreen.NoticeSeedPhraseSupportButtonDeclined) - viewModelScope.launch { + modelScope.launch { seedPhraseNotificationUseCase.rejectSecond(userWalletId = userWallet.walletId) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureModule.kt new file mode 100644 index 0000000000..22cf0f0df5 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureModule.kt @@ -0,0 +1,31 @@ +package com.tangem.feature.wallet.di + +import com.tangem.core.decompose.model.Model +import com.tangem.feature.wallet.DefaultWalletEntryComponent +import com.tangem.feature.wallet.child.organizetokens.model.OrganizeTokensModel +import com.tangem.feature.wallet.child.wallet.model.WalletModel +import com.tangem.features.wallet.WalletEntryComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface WalletFeatureModule { + + @Binds + fun bindComponentFactory(impl: DefaultWalletEntryComponent.Factory): WalletEntryComponent.Factory + + @Binds + @IntoMap + @ClassKey(WalletModel::class) + fun bindWalletModel(model: WalletModel): Model + + @Binds + @IntoMap + @ClassKey(OrganizeTokensModel::class) + fun bindOrganizeTokensModel(model: OrganizeTokensModel): Model +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletRouterModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletRouterModule.kt index 4d110cae5d..f82f682933 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletRouterModule.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletRouterModule.kt @@ -1,18 +1,18 @@ package com.tangem.feature.wallet.di +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.di.ModelScoped import com.tangem.feature.wallet.presentation.router.DefaultWalletRouter -import com.tangem.features.wallet.navigation.WalletRouter +import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import dagger.Binds import dagger.Module import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityComponent -import dagger.hilt.android.scopes.ActivityScoped @Module -@InstallIn(ActivityComponent::class) +@InstallIn(ModelComponent::class) internal interface WalletRouterModule { @Binds - @ActivityScoped - fun bindsWalletRouter(defaultWalletRouter: DefaultWalletRouter): WalletRouter + @ModelScoped + fun bindsWalletRouter(defaultWalletRouter: DefaultWalletRouter): InnerWalletRouter } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/navigation/WalletRoute.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/navigation/WalletRoute.kt new file mode 100644 index 0000000000..54381da20a --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/navigation/WalletRoute.kt @@ -0,0 +1,14 @@ +package com.tangem.feature.wallet.navigation + +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.serialization.Serializable + +@Serializable +internal sealed class WalletRoute { + + @Serializable + data object Wallet : WalletRoute() + + @Serializable + data class OrganizeTokens(val userWalletId: UserWalletId) : WalletRoute() +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt deleted file mode 100644 index d8a119f840..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.tangem.feature.wallet.presentation - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import com.arkivanov.decompose.defaultComponentContext -import com.tangem.common.routing.AppRouter -import com.tangem.common.routing.utils.asRouter -import com.tangem.core.decompose.context.DefaultAppComponentContext -import com.tangem.core.decompose.di.DecomposeComponent -import com.tangem.core.decompose.di.GlobalUiMessageSender -import com.tangem.core.decompose.ui.UiMessageSender -import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.feature.wallet.presentation.router.InnerWalletRouter -import com.tangem.features.wallet.navigation.WalletRouter -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -/** - * Wallet fragment - * -[REDACTED_AUTHOR] - */ -@AndroidEntryPoint -internal class WalletFragment : ComposeFragment() { - - @Inject - override lateinit var uiDependencies: UiDependencies - - /** Feature router */ - @Inject - internal lateinit var walletRouter: WalletRouter - - @Inject - internal lateinit var coroutineDispatcherProvider: CoroutineDispatcherProvider - - @Inject - internal lateinit var componentBuilder: DecomposeComponent.Builder - - @Inject - internal lateinit var appRouter: AppRouter - - @Inject - @GlobalUiMessageSender - internal lateinit var messageSender: UiMessageSender - - private val _walletRouter: InnerWalletRouter - get() = requireNotNull(walletRouter as? InnerWalletRouter) { - "_walletRouter should be instance of InnerWalletRouter" - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - _walletRouter.initializeResources( - appComponentContext = DefaultAppComponentContext( - componentContext = defaultComponentContext(requireActivity().onBackPressedDispatcher), - messageSender = messageSender, - dispatchers = coroutineDispatcherProvider, - hiltComponentBuilder = componentBuilder, - replaceRouter = appRouter.asRouter(), - ), - ) - } - - @Composable - override fun ScreenContent(modifier: Modifier) { - _walletRouter.Initialize( - onFinish = remember(requireActivity()) { - { - requireActivity().finish() - } - }, - ) - } - - companion object { - - /** Create wallet fragment instance */ - fun create(): WalletFragment = WalletFragment() - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index f77bbed840..94875479d9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -8,11 +8,16 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemColorPalette import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.wallet.presentation.wallet.state.model.ActionsBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.TokenActionButtonConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBottomSheetConfig import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState import com.tangem.feature.wallet.presentation.wallet.state.model.* +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toPersistentList import java.util.UUID @@ -32,8 +37,7 @@ internal object WalletPreviewData { content = TextReference.Str("3 cards • Seed phrase3 cards • Seed phrasephrasephrasephrase"), ), imageResId = R.drawable.ill_wallet2_cards3_120_106, - onRenameClick = { _ -> }, - onDeleteClick = {}, + dropDownItems = persistentListOf(), cardCount = 1, isZeroBalance = false, isBalanceFlickering = false, @@ -45,8 +49,7 @@ internal object WalletPreviewData { id = UserWalletId("321"), title = "Wallet 1", imageResId = R.drawable.ill_wallet2_cards3_120_106, - onRenameClick = { _ -> }, - onDeleteClick = {}, + dropDownItems = persistentListOf(), ) } @@ -55,8 +58,7 @@ internal object WalletPreviewData { id = UserWalletId("24"), title = "Wallet 1", imageResId = R.drawable.ill_wallet2_cards3_120_106, - onRenameClick = { _ -> }, - onDeleteClick = {}, + dropDownItems = persistentListOf(), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt index 6bb90675a5..4f8cb9bdfd 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt @@ -9,6 +9,7 @@ import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.common.WalletPreviewData.topBarConfig import com.tangem.feature.wallet.presentation.wallet.state.model.* @@ -87,8 +88,7 @@ internal object WalletScreenPreviewData { content = TextReference.Str("Locked"), ), imageResId = R.drawable.ill_note_btc_120_106, - onRenameClick = { _ -> }, - onDeleteClick = {}, + dropDownItems = persistentListOf(), ) } private val miltiUnreachableCard by lazy { @@ -102,8 +102,7 @@ internal object WalletScreenPreviewData { imageResId = R.drawable.ill_wallet2_cards3_120_106, cardCount = 3, balance = DASH_SIGN, - onRenameClick = { _ -> }, - onDeleteClick = {}, + dropDownItems = persistentListOf(), isZeroBalance = false, isBalanceFlickering = false, ) @@ -146,7 +145,6 @@ internal object WalletScreenPreviewData { ) internal val walletScreenState = WalletScreenState( - onBackClick = {}, topBarConfig = topBarConfig, selectedWalletIndex = 0, wallets = persistentListOf( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt index e019c8dd88..a0b28f06fd 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt @@ -1,7 +1,5 @@ package com.tangem.feature.wallet.presentation.deeplink -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.api.AnalyticsEventHandler @@ -13,8 +11,9 @@ import com.tangem.domain.tokens.GetCryptoCurrencyUseCase import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.features.onramp.OnrampFeatureToggles +import com.tangem.utils.coroutines.launchOnCancellation import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import timber.log.Timber @@ -32,14 +31,14 @@ internal class WalletDeepLinksHandler @Inject constructor( private var deepLinksMap = mutableMapOf>() - fun registerForWallet(viewModel: ViewModel, userWallet: UserWallet) { + fun registerForWallet(scope: CoroutineScope, userWallet: UserWallet) { val deepLinks = deepLinksMap.getOrPut(userWallet.walletId) { - getDeepLinks(userWallet, viewModel.viewModelScope) + getDeepLinks(userWallet, scope) } deepLinksRegistry.unregisterByIds(deepLinks.map { it.id }) deepLinksRegistry.register(deepLinks = deepLinks) - viewModel.addCloseable { + scope.launchOnCancellation { deepLinksRegistry.unregister(deepLinks) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/converter/items/CryptoCurrencyToDraggableItemConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/converter/items/CryptoCurrencyToDraggableItemConverter.kt index 15ca6ab348..c7302dd54a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/converter/items/CryptoCurrencyToDraggableItemConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/converter/items/CryptoCurrencyToDraggableItemConverter.kt @@ -12,8 +12,10 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getGroupHeaderId import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getTokenItemId +import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.Provider import com.tangem.utils.converter.Converter +import com.tangem.utils.extensions.orZero import java.math.BigDecimal internal class CryptoCurrencyToDraggableItemConverter( @@ -63,7 +65,11 @@ internal class CryptoCurrencyToDraggableItemConverter( private fun getFormattedFiatAmount(currency: CryptoCurrencyStatus, appCurrency: AppCurrency): String { val yieldBalance = currency.value.yieldBalance as? YieldBalance.Data val fiatRate = currency.value.fiatRate ?: BigDecimal.ZERO - val fiatYieldBalance = yieldBalance?.getTotalWithRewardsStakingBalance()?.multiply(fiatRate) ?: BigDecimal.ZERO + val fiatYieldBalance = if (BlockchainUtils.isIncludeStakingTotalBalance(currency.currency.network.id.value)) { + yieldBalance?.getTotalWithRewardsStakingBalance()?.multiply(fiatRate).orZero() + } else { + BigDecimal.ZERO + } val fiatAmount = currency.value.fiatAmount ?: return BigDecimalFormatConstants.EMPTY_BALANCE_SIGN return (fiatAmount + fiatYieldBalance).format { fiat(appCurrency.code, appCurrency.symbol) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index 384435d2a8..fe1c52f5bc 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -1,144 +1,41 @@ package com.tangem.feature.wallet.presentation.router -import android.annotation.SuppressLint -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.fragment.app.Fragment -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavHostController -import androidx.navigation.NavType -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import androidx.navigation.navArgument -import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState -import com.arkivanov.decompose.router.slot.ChildSlot import com.arkivanov.decompose.router.slot.SlotNavigation -import com.arkivanov.decompose.router.slot.childSlot -import com.arkivanov.decompose.router.slot.dismiss -import com.arkivanov.decompose.value.Value import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRoute.ManageTokens.Source import com.tangem.common.routing.AppRouter -import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.decompose.context.childByContext +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.navigation.url.UrlOpener -import com.tangem.core.ui.decompose.ComposableDialogComponent import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.redux.StateDialog import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.WalletFragment -import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScreen -import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel +import com.tangem.feature.wallet.navigation.WalletRoute import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig -import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel -import com.tangem.feature.walletsettings.component.RenameWalletComponent -import com.tangem.features.markets.entry.MarketsEntryComponent import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow import javax.inject.Inject -import kotlin.properties.Delegates /** Default implementation of wallet feature router */ +@ModelScoped internal class DefaultWalletRouter @Inject constructor( private val router: AppRouter, private val urlOpener: UrlOpener, private val reduxStateHolder: ReduxStateHolder, private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles, - private val marketsEntryComponentFactory: MarketsEntryComponent.Factory, - private val renameWalletComponentFactory: RenameWalletComponent.Factory, ) : InnerWalletRouter { - private var navController: NavHostController by Delegates.notNull() - private var onFinish: () -> Unit = {} - - private lateinit var marketsEntryComponent: MarketsEntryComponent - private lateinit var dialog: Value> - override val dialogNavigation: SlotNavigation = SlotNavigation() - override fun initializeResources(appComponentContext: AppComponentContext) { - marketsEntryComponent = marketsEntryComponentFactory.create(appComponentContext) - dialog = appComponentContext.childSlot( - source = dialogNavigation, - serializer = WalletDialogConfig.serializer(), - handleBackButton = true, - childFactory = { dialogConfig, componentContext -> - dialogChild( - appContext = appComponentContext, - dialogConfig = dialogConfig, - componentContext = componentContext, - ) - }, - ) - } - - override fun getEntryFragment(): Fragment = WalletFragment.create() - - @Composable - override fun Initialize(onFinish: () -> Unit) { - this.onFinish = onFinish - - NavHost( - navController = rememberNavController().apply { navController = this }, - startDestination = WalletRoute.Wallet.route, - ) { - composable(WalletRoute.Wallet.route) { - val viewModel = hiltViewModel().apply { - setWalletRouter(router = this@DefaultWalletRouter) - subscribeToLifecycle(LocalLifecycleOwner.current) - } - - val dialog by dialog.subscribeAsState() - - WalletScreen( - state = viewModel.uiState.collectAsStateWithLifecycle().value, - marketsEntryComponent = marketsEntryComponent, - ) - - dialog.child?.instance?.Dialog() - } - - composable( - WalletRoute.OrganizeTokens.route, - arguments = listOf(navArgument(WalletRoute.userWalletIdKey) { type = NavType.StringType }), - ) { - val viewModel = hiltViewModel().apply { - router = this@DefaultWalletRouter - } - LocalLifecycleOwner.current.lifecycle.addObserver(viewModel) - - val uiState by viewModel.uiState.collectAsStateWithLifecycle() - - OrganizeTokensScreen( - state = uiState, - ) - } - } - } - - @SuppressLint("RestrictedApi") - override fun popBackStack() { - /* - * It's hack that avoid issue with closing the wallet screen. - * We are using NavGraph only inside feature so first backstack's element is entry of NavGraph and - * next element is wallet screen entry. - * If backstack contains only NavGraph entry and wallet screen entry then we close the wallet fragment. - */ - if (navController.currentBackStack.value.size == BACKSTACK_ENTRY_COUNT_TO_CLOSE_WALLET_SCREEN) { - onFinish.invoke() - } else { - navController.popBackStack() - } - } + override val navigateToFlow = MutableSharedFlow( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_LATEST, + ) override fun openOrganizeTokensScreen(userWalletId: UserWalletId) { - navController.navigate(WalletRoute.OrganizeTokens.createRoute(userWalletId)) + navigateToFlow.tryEmit(WalletRoute.OrganizeTokens(userWalletId)) } override fun openDetailsScreen(selectedWalletId: UserWalletId) { @@ -215,25 +112,4 @@ internal class DefaultWalletRouter @Inject constructor( override fun openScanFailedDialog(onTryAgain: () -> Unit) { reduxStateHolder.dispatchDialogShow(StateDialog.ScanFailsDialog(StateDialog.ScanFailsSource.MAIN, onTryAgain)) } - - private fun dialogChild( - appContext: AppComponentContext, - dialogConfig: WalletDialogConfig, - componentContext: ComponentContext, - ): ComposableDialogComponent = when (dialogConfig) { - is WalletDialogConfig.RenameWallet -> { - renameWalletComponentFactory.create( - context = appContext.childByContext(componentContext), - params = RenameWalletComponent.Params( - userWalletId = dialogConfig.userWalletId, - currentName = dialogConfig.currentName, - onDismiss = dialogNavigation::dismiss, - ), - ) - } - } - - private companion object { - const val BACKSTACK_ENTRY_COUNT_TO_CLOSE_WALLET_SCREEN = 2 - } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt index f20dd1c443..9ec77cb66c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt @@ -1,14 +1,13 @@ package com.tangem.feature.wallet.presentation.router -import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import com.arkivanov.decompose.router.slot.SlotNavigation -import com.tangem.core.decompose.context.AppComponentContext import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.wallet.navigation.WalletRoute import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig -import com.tangem.features.wallet.navigation.WalletRouter +import kotlinx.coroutines.flow.SharedFlow /** * Interface of inner wallet feature router @@ -19,22 +18,11 @@ import com.tangem.features.wallet.navigation.WalletRouter [REDACTED_AUTHOR] */ @Stable -internal interface InnerWalletRouter : WalletRouter { +internal interface InnerWalletRouter { val dialogNavigation: SlotNavigation - fun initializeResources(appComponentContext: AppComponentContext) - - /** - * Initialize router - * - * @param onFinish finish activity callback - */ - @Composable - fun Initialize(onFinish: () -> Unit) - - /** Pop back stack */ - fun popBackStack() + val navigateToFlow: SharedFlow /** Open organize tokens screen */ fun openOrganizeTokensScreen(userWalletId: UserWalletId) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/WalletRoute.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/WalletRoute.kt deleted file mode 100644 index 2cb8a4a91c..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/WalletRoute.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.tangem.feature.wallet.presentation.router - -import com.tangem.domain.wallets.models.UserWalletId - -/** - * Wallet feature screens - * - * @property route route string representation - * -[REDACTED_AUTHOR] - */ -internal sealed class WalletRoute(val route: String) { - - object Wallet : WalletRoute(route = "wallet") - - object OrganizeTokens : WalletRoute(route = "wallet/{$userWalletIdKey}/organize_tokens") { - - fun createRoute(userWalletId: UserWalletId) = "wallet/${userWalletId.stringValue}/organize_tokens" - } - - companion object { - const val userWalletIdKey = "userWalletId" - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt index a475d10af8..b057ac8dd5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt @@ -8,6 +8,7 @@ import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.common.extensions.isZero import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.analytics.CheckIsWalletToppedUpUseCase import com.tangem.domain.analytics.model.WalletBalanceState import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -19,13 +20,12 @@ import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnaly import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider -import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import java.math.BigDecimal import javax.inject.Inject -@ViewModelScoped +@ModelScoped internal class TokenListAnalyticsSender @Inject constructor( private val analyticsEventHandler: AnalyticsEventHandler, private val checkIsWalletToppedUpUseCase: CheckIsWalletToppedUpUseCase, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt index cc8e0c9c5e..36ed315d4b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt @@ -3,16 +3,16 @@ package com.tangem.feature.wallet.presentation.wallet.analytics.utils import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent.ProgramName import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider -import dagger.hilt.android.scopes.ViewModelScoped import javax.inject.Inject -@ViewModelScoped +@ModelScoped internal class WalletWarningsAnalyticsSender @Inject constructor( private val analyticsEventHandler: AnalyticsEventHandler, private val screenLifecycleProvider: ScreenLifecycleProvider, @@ -59,6 +59,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor( is WalletNotification.Warning.SomeNetworksUnreachable, is WalletNotification.Warning.NetworksUnreachable, is WalletNotification.UsedOutdatedData, + is WalletNotification.UnlockVisaAccess, -> null is WalletNotification.Critical.SeedPhraseNotification -> MainScreen.NoticeSeedPhraseSupport is WalletNotification.Critical.SeedPhraseSecondNotification -> MainScreen.NoticeSeedPhraseSupportSecond diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsSingleEventSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsSingleEventSender.kt index e00a7a429b..80735cc935 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsSingleEventSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsSingleEventSender.kt @@ -1,14 +1,14 @@ package com.tangem.feature.wallet.presentation.wallet.analytics.utils +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.SeedPhraseNotificationUseCase import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider -import dagger.hilt.android.scopes.ViewModelScoped import javax.inject.Inject -@ViewModelScoped +@ModelScoped internal class WalletWarningsSingleEventSender @Inject constructor( private val seedPhraseNotificationUseCase: SeedPhraseNotificationUseCase, private val screenLifecycleProvider: ScreenLifecycleProvider, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index 5a5fa3536e..6fe75e5285 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.domain +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.core.lce.Lce @@ -16,8 +17,7 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase import com.tangem.domain.wallets.usecase.SeedPhraseNotificationUseCase import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents -import dagger.hilt.android.scopes.ViewModelScoped +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.Flow @@ -26,7 +26,7 @@ import javax.inject.Inject import kotlin.collections.count @Suppress("LongParameterList") -@ViewModelScoped +@ModelScoped internal class GetMultiWalletWarningsFactory @Inject constructor( private val tokenListStore: MultiWalletTokenListStore, private val isDemoCardUseCase: IsDemoCardUseCase, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt index 4e1f2ca1f1..b59d4bd433 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt @@ -1,6 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.domain import arrow.core.Either +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.demo.IsDemoCardUseCase @@ -13,14 +14,13 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents -import dagger.hilt.android.scopes.ViewModelScoped +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.* import javax.inject.Inject -@ViewModelScoped +@ModelScoped internal class GetSingleWalletWarningsFactory @Inject constructor( private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, private val isDemoCardUseCase: IsDemoCardUseCase, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/HasSingleWalletSignedHashesUseCase.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/HasSingleWalletSignedHashesUseCase.kt index 0738db4676..7c8d7ab2b4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/HasSingleWalletSignedHashesUseCase.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/HasSingleWalletSignedHashesUseCase.kt @@ -1,17 +1,17 @@ package com.tangem.feature.wallet.presentation.wallet.domain +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.demo.DemoConfig import com.tangem.domain.tokens.model.Network import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWallet -import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import javax.inject.Inject -@ViewModelScoped +@ModelScoped class HasSingleWalletSignedHashesUseCase @Inject constructor( private val cardRepository: CardRepository, private val walletManagersFacade: WalletManagersFacade, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/MultiWalletTokenListStore.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/MultiWalletTokenListStore.kt index d25f60ca3e..82e1af33a4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/MultiWalletTokenListStore.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/MultiWalletTokenListStore.kt @@ -1,11 +1,11 @@ package com.tangem.feature.wallet.presentation.wallet.domain +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWalletId -import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.SharingStarted @@ -14,7 +14,7 @@ import timber.log.Timber import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject -@ViewModelScoped +@ModelScoped internal class MultiWalletTokenListStore @Inject constructor( private val getTokenListUseCase: GetTokenListUseCase, ) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt index 32450b5201..3c1961eee0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt @@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.domain import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.decompose.di.ModelScoped import com.tangem.datasource.local.swaptx.ExpressAnalyticsStatus import com.tangem.domain.onramp.GetOnrampStatusUseCase import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase @@ -13,14 +14,13 @@ import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject -@ViewModelScoped +@ModelScoped internal class OnrampStatusFactory @Inject constructor( private val stateHolder: WalletStateController, private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/WalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/WalletContentLoaderFactory.kt index cbb2dad87f..95cb580695 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/WalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/WalletContentLoaderFactory.kt @@ -1,13 +1,13 @@ package com.tangem.feature.wallet.presentation.wallet.loaders +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.loaders.implementors.* -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents -import dagger.hilt.android.scopes.ViewModelScoped import javax.inject.Inject -@ViewModelScoped +@ModelScoped internal class WalletContentLoaderFactory @Inject constructor( private val multiWalletContentLoaderFactory: MultiWalletContentLoaderFactory, private val singleWalletWithTokenContentLoaderFactory: SingleWalletWithTokenContentLoaderFactory, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/WalletScreenContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/WalletScreenContentLoader.kt index 66990ac417..83675a0199 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/WalletScreenContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/WalletScreenContentLoader.kt @@ -1,10 +1,10 @@ package com.tangem.feature.wallet.presentation.wallet.loaders +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.CoroutineScope import timber.log.Timber import javax.inject.Inject @@ -18,7 +18,7 @@ import javax.inject.Inject * [REDACTED_AUTHOR] */ -@ViewModelScoped +@ModelScoped internal class WalletScreenContentLoader @Inject constructor( private val factory: WalletContentLoaderFactory, private val storage: WalletLoaderStorage, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt index b5c157f8b4..402b03dec0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt @@ -1,11 +1,14 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.promo.GetStoryContentUseCase import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsSingleEventSender @@ -13,14 +16,15 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.subscribers.* import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletActionButtonsSubscriber import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletTokenListSubscriber import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.features.swap.SwapFeatureToggles @Suppress("LongParameterList") +@ModelScoped internal class MultiWalletContentLoader( private val userWallet: UserWallet, private val stateHolder: WalletStateController, @@ -34,6 +38,7 @@ internal class MultiWalletContentLoader( private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, + private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase, private val getStoryContentUseCase: GetStoryContentUseCase, private val swapFeatureToggles: SwapFeatureToggles, private val deepLinksRegistry: DeepLinksRegistry, @@ -68,6 +73,11 @@ internal class MultiWalletContentLoader( getStoryContentUseCase = getStoryContentUseCase, ).let(::add) } + WalletDropDownItemsSubscriber( + stateHolder = stateHolder, + shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase, + clickIntents = clickIntents, + ).let(::add) } } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt index 0196a960d0..7bc706075d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt @@ -1,11 +1,14 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.promo.GetStoryContentUseCase import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsSingleEventSender @@ -13,13 +16,11 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.features.swap.SwapFeatureToggles -import dagger.hilt.android.scopes.ViewModelScoped import javax.inject.Inject @Suppress("LongParameterList") -@ViewModelScoped +@ModelScoped internal class MultiWalletContentLoaderFactory @Inject constructor( private val stateHolder: WalletStateController, private val tokenListAnalyticsSender: TokenListAnalyticsSender, @@ -31,6 +32,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender, private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, + private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase, private val getStoryContentUseCase: GetStoryContentUseCase, private val swapFeatureToggles: SwapFeatureToggles, private val deepLinksRegistry: DeepLinksRegistry, @@ -51,6 +53,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( applyTokenListSortingUseCase = applyTokenListSortingUseCase, runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, getStoryContentUseCase = getStoryContentUseCase, + shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase, swapFeatureToggles = swapFeatureToggles, deepLinksRegistry = deepLinksRegistry, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt index 0d96fb7855..e4167b9c20 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt @@ -10,11 +10,18 @@ import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetSingleWalletWarningsFactory -import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.subscribers.* -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.presentation.wallet.subscribers.PrimaryCurrencySubscriber +import com.tangem.feature.wallet.presentation.wallet.subscribers.SingleWalletButtonsSubscriber +import com.tangem.feature.wallet.presentation.wallet.subscribers.SingleWalletNotificationsSubscriber +import com.tangem.feature.wallet.presentation.wallet.subscribers.TxHistorySubscriber +import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletDropDownItemsSubscriber +import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber @Suppress("LongParameterList") internal class SingleWalletContentLoader( @@ -31,6 +38,7 @@ internal class SingleWalletContentLoader( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase, private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase, + private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase, private val analyticsEventHandler: AnalyticsEventHandler, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, ) : WalletContentLoader(id = userWallet.walletId) { @@ -59,6 +67,11 @@ internal class SingleWalletContentLoader( getSingleWalletWarningsFactory = getSingleWalletWarningsFactory, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, ), + WalletDropDownItemsSubscriber( + stateHolder = stateHolder, + shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase, + clickIntents = clickIntents, + ), SingleWalletExpressStatusesSubscriber( userWallet = userWallet, stateHolder = stateHolder, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt index aa40c79e7f..6e8e164e92 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt @@ -1,6 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.onramp.GetOnrampTransactionsUseCase import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase @@ -10,14 +11,14 @@ import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetSingleWalletWarningsFactory import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents -import dagger.hilt.android.scopes.ViewModelScoped +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import javax.inject.Inject -@ViewModelScoped +@ModelScoped @Suppress("LongParameterList") internal class SingleWalletContentLoaderFactory @Inject constructor( private val stateHolder: WalletStateController, @@ -30,6 +31,7 @@ internal class SingleWalletContentLoaderFactory @Inject constructor( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase, private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase, + private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase, private val analyticsEventHandler: AnalyticsEventHandler, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, ) { @@ -51,6 +53,7 @@ internal class SingleWalletContentLoaderFactory @Inject constructor( walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, getOnrampTransactionsUseCase = getOnrampTransactionsUseCase, onrampRemoveTransactionUseCase = onrampRemoveTransactionUseCase, + shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt index e87a5d2d10..fd9277c21a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt @@ -5,6 +5,8 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.promo.GetStoryContentUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsSingleEventSender @@ -12,11 +14,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletActionButtonsSubscriber -import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber -import com.tangem.feature.wallet.presentation.wallet.subscribers.SingleWalletWithTokenListSubscriber -import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.presentation.wallet.subscribers.* import com.tangem.features.swap.SwapFeatureToggles @Suppress("LongParameterList") @@ -32,6 +30,7 @@ internal class SingleWalletWithTokenContentLoader( private val tokenListStore: MultiWalletTokenListStore, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, + private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase, private val getStoryContentUseCase: GetStoryContentUseCase, private val swapFeatureToggles: SwapFeatureToggles, private val deepLinksRegistry: DeepLinksRegistry, @@ -65,6 +64,11 @@ internal class SingleWalletWithTokenContentLoader( getStoryContentUseCase = getStoryContentUseCase, ).let(::add) } + WalletDropDownItemsSubscriber( + stateHolder = stateHolder, + shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase, + clickIntents = clickIntents, + ).let(::add) } } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt index 002f3fc487..1a7e15d40d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt @@ -1,10 +1,12 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors +import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.promo.GetStoryContentUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsSingleEventSender @@ -12,12 +14,13 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.features.swap.SwapFeatureToggles import javax.inject.Inject // TODO: Refactor @Suppress("LongParameterList") +@ModelScoped internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( private val stateHolder: WalletStateController, private val tokenListAnalyticsSender: TokenListAnalyticsSender, @@ -28,6 +31,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender, private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, + private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase, private val getStoryContentUseCase: GetStoryContentUseCase, private val swapFeatureToggles: SwapFeatureToggles, private val deepLinksRegistry: DeepLinksRegistry, @@ -47,6 +51,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( walletWarningsSingleEventSender = walletWarningsSingleEventSender, runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, getStoryContentUseCase = getStoryContentUseCase, + shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase, swapFeatureToggles = swapFeatureToggles, deepLinksRegistry = deepLinksRegistry, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/VisaWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/VisaWalletContentLoader.kt index 63c0590221..496e989123 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/VisaWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/VisaWalletContentLoader.kt @@ -6,7 +6,7 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.subscribers.VisaWalletSubscriber import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents internal class VisaWalletContentLoader( private val userWallet: UserWallet, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/VisaWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/VisaWalletContentLoaderFactory.kt index ff7033b81d..30dcbd7652 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/VisaWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/VisaWalletContentLoaderFactory.kt @@ -1,14 +1,14 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors +import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.visa.GetVisaCurrencyUseCase import com.tangem.domain.visa.GetVisaTxHistoryUseCase import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents -import dagger.hilt.android.scopes.ViewModelScoped import javax.inject.Inject -@ViewModelScoped +@ModelScoped internal class VisaWalletContentLoaderFactory @Inject constructor( private val stateHolder: WalletStateController, private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateController.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateController.kt index 6114dfeaf6..cf9c56b44c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateController.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateController.kt @@ -87,7 +87,6 @@ internal class WalletStateController @Inject constructor() { private fun getInitialState(): WalletScreenState { return WalletScreenState( - onBackClick = {}, topBarConfig = WalletTopBarConfig(onDetailsClick = {}), selectedWalletIndex = NOT_INITIALIZED_WALLET_INDEX, wallets = persistentListOf(), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/BalancesAndLimitsBottomSheetConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/BalancesAndLimitsBottomSheetConfig.kt index 1dd961ab14..cedb03bee2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/BalancesAndLimitsBottomSheetConfig.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/BalancesAndLimitsBottomSheetConfig.kt @@ -12,7 +12,6 @@ internal data class BalancesAndLimitsBottomSheetConfig( val availableBalance: String, val blockedBalance: String, val debit: String, - val pending: String, val amlVerified: String, val onInfoClick: () -> Unit, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletCardState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletCardState.kt index c03ee58d88..2be0662c6c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletCardState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletCardState.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.Immutable import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.StringsSigns.DASH_SIGN +import kotlinx.collections.immutable.ImmutableList /** Wallet card state */ @Immutable @@ -23,11 +24,8 @@ internal sealed interface WalletCardState { @get:DrawableRes val imageResId: Int? - /** Lambda be invoked when Rename button is clicked */ - val onRenameClick: (UserWalletId) -> Unit - - /** Lambda be invoked when Delete button is clicked */ - val onDeleteClick: (UserWalletId) -> Unit + /** Wallet drop down items */ + val dropDownItems: ImmutableList /** * Wallet card content state @@ -35,8 +33,7 @@ internal sealed interface WalletCardState { * @property id wallet id * @property title wallet name * @property imageResId wallet image resource id - * @property onRenameClick lambda be invoked when Rename button is clicked - * @property onDeleteClick lambda be invoked when Delete button is clicked + * @property dropDownItems wallet dropdown items * @property additionalInfo wallet additional info * @property cardCount number of cards in the wallet * @property balance wallet balance @@ -46,8 +43,7 @@ internal sealed interface WalletCardState { override val title: String, override val additionalInfo: WalletAdditionalInfo, override val imageResId: Int?, - override val onRenameClick: (UserWalletId) -> Unit, - override val onDeleteClick: (UserWalletId) -> Unit, + override val dropDownItems: ImmutableList, val isBalanceFlickering: Boolean, val cardCount: Int?, val balance: String, @@ -61,16 +57,14 @@ internal sealed interface WalletCardState { * @property title wallet name * @property additionalInfo wallet additional info * @property imageResId wallet image resource id - * @property onRenameClick lambda be invoked when Rename button is clicked - * @property onDeleteClick lambda be invoked when Delete button is clicked + * @property dropDownItems wallet dropdown items */ data class LockedContent( override val id: UserWalletId, override val title: String, override val additionalInfo: WalletAdditionalInfo, override val imageResId: Int?, - override val onRenameClick: (UserWalletId) -> Unit, - override val onDeleteClick: (UserWalletId) -> Unit, + override val dropDownItems: ImmutableList, ) : WalletCardState /** @@ -79,16 +73,14 @@ internal sealed interface WalletCardState { * @property id wallet id * @property title wallet name * @property imageResId wallet image resource id - * @property onRenameClick lambda be invoked when Rename button is clicked - * @property onDeleteClick lambda be invoked when Delete button is clicked + * @property dropDownItems wallet dropdown items */ data class Error( override val id: UserWalletId, override val title: String, override val additionalInfo: WalletAdditionalInfo? = defaultAdditionalInfo, override val imageResId: Int?, - override val onRenameClick: (UserWalletId) -> Unit, - override val onDeleteClick: (UserWalletId) -> Unit, + override val dropDownItems: ImmutableList, ) : WalletCardState { private companion object { @@ -103,24 +95,25 @@ internal sealed interface WalletCardState { * @property id wallet id * @property title wallet name * @property imageResId wallet image resource id - * @property onRenameClick lambda be invoked when Rename button is clicked - * @property onDeleteClick lambda be invoked when Delete button is clicked + * @property dropDownItems wallet dropdown items */ data class Loading( override val id: UserWalletId, override val title: String, override val additionalInfo: WalletAdditionalInfo? = null, override val imageResId: Int?, - override val onRenameClick: (UserWalletId) -> Unit, - override val onDeleteClick: (UserWalletId) -> Unit, + override val dropDownItems: ImmutableList, ) : WalletCardState - fun copySealed(title: String = this.title): WalletCardState { + fun copySealed( + title: String = this.title, + dropDownItems: ImmutableList = this.dropDownItems, + ): WalletCardState { return when (this) { - is Content -> copy(title = title) - is Error -> copy(title = title) - is Loading -> copy(title = title) - is LockedContent -> copy(title = title) + is Content -> copy(title = title, dropDownItems = dropDownItems) + is Error -> copy(title = title, dropDownItems = dropDownItems) + is Loading -> copy(title = title, dropDownItems = dropDownItems) + is LockedContent -> copy(title = title, dropDownItems = dropDownItems) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletDropDownItems.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletDropDownItems.kt new file mode 100644 index 0000000000..a4270e37ee --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletDropDownItems.kt @@ -0,0 +1,10 @@ +package com.tangem.feature.wallet.presentation.wallet.state.model + +import androidx.annotation.DrawableRes +import com.tangem.core.ui.extensions.TextReference + +internal data class WalletDropDownItems( + val text: TextReference, + @DrawableRes val icon: Int, + val onClick: () -> Unit, +) \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNFTItemUM.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNFTItemUM.kt new file mode 100644 index 0000000000..1d2dd2b8ba --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNFTItemUM.kt @@ -0,0 +1,30 @@ +package com.tangem.feature.wallet.presentation.wallet.state.model + +import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableList + +@Immutable +sealed class WalletNFTItemUM { + + data object Hidden : WalletNFTItemUM() + + data object Loading : WalletNFTItemUM() + + data object Empty : WalletNFTItemUM() + + data object Failed : WalletNFTItemUM() + + data class Content( + val previews: ImmutableList, + val collectionsCount: Int, + val assetsCount: Int, + val isFlickering: Boolean, + ) : WalletNFTItemUM() { + + @Immutable + sealed class CollectionPreview { + data class Image(val url: String) : CollectionPreview() + data object More : CollectionPreview() + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt index 6cb194fd18..203a35a3a8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt @@ -191,6 +191,19 @@ sealed class WalletNotification(val config: NotificationConfig) { ), ) + data class UnlockVisaAccess(val onUnlockClick: () -> Unit) : WalletNotification( + config = NotificationConfig( + title = resourceReference(id = R.string.visa_unlock_notification_title), + subtitle = resourceReference(id = R.string.visa_unlock_notification_subtitle), + iconResId = R.drawable.ic_locked_24, + buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig( + text = resourceReference(id = R.string.visa_unlock_notification_button), + iconResId = R.drawable.ic_tangem_24, + onClick = onUnlockClick, + ), + ), + ) + data class RateApp( val onLikeClick: () -> Unit, val onDislikeClick: () -> Unit, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletScreenState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletScreenState.kt index 934382263b..df260b5856 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletScreenState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletScreenState.kt @@ -6,7 +6,6 @@ import kotlinx.collections.immutable.ImmutableList @Immutable internal data class WalletScreenState( - val onBackClick: () -> Unit, val topBarConfig: WalletTopBarConfig, val selectedWalletIndex: Int, val wallets: ImmutableList, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt index 885e370725..42b4839fc4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt @@ -12,6 +12,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistor import com.tangem.feature.wallet.presentation.wallet.state.model.holder.WalletStateHolder import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.persistentListOf internal const val NOT_INITIALIZED_WALLET_INDEX = -1 @@ -115,5 +116,27 @@ internal sealed interface WalletState : WalletStateHolder { override val balancesAndLimitBlockState: BalancesAndLimitsBlockState? = null } + + data class AccessTokenLocked( + override val walletCardState: WalletCardState, + override val buttons: PersistentList, + override val bottomSheetConfig: TangemBottomSheetConfig?, + val onExploreClick: () -> Unit, + val onUnlockVisaAccessNotificationClick: () -> Unit, + ) : Visa(), + TxHistoryStateHolder by LockedTxHistoryStateHolder(onExploreClick), + WalletStateHolder by LockedWalletStateHolder( + walletCardState = walletCardState, + buttons = buttons, + bottomSheetConfig = bottomSheetConfig, + onUnlockNotificationClick = {}, + ) { + + override val warnings: ImmutableList = persistentListOf( + WalletNotification.UnlockVisaAccess(onUnlockClick = onUnlockVisaAccessNotificationClick), + ) + + override val balancesAndLimitBlockState: BalancesAndLimitsBlockState? = null + } } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/AddWalletTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/AddWalletTransformer.kt index 86e534351d..72d2e0f783 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/AddWalletTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/AddWalletTransformer.kt @@ -4,7 +4,7 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import kotlinx.collections.immutable.toImmutableList diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/CloseBottomSheetTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/CloseBottomSheetTransformer.kt index feeea1a264..8bfcd6adaf 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/CloseBottomSheetTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/CloseBottomSheetTransformer.kt @@ -25,6 +25,9 @@ internal class CloseBottomSheetTransformer(userWalletId: UserWalletId) : WalletS is WalletState.Visa.Locked -> prevState.copy( bottomSheetConfig = updateConfig(prevState), ) + is WalletState.Visa.AccessTokenLocked -> prevState.copy( + bottomSheetConfig = updateConfig(prevState), + ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt index bfa6f1caa6..ac0c59bc42 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt @@ -7,7 +7,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver import com.tangem.feature.wallet.presentation.wallet.state.model.* import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory import com.tangem.feature.wallet.presentation.wallet.state.utils.createStateByWalletType -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf @@ -31,7 +31,6 @@ internal class InitializeWalletsTransformer( override fun transform(prevState: WalletScreenState): WalletScreenState { return prevState.copy( - onBackClick = clickIntents::onBackClick, topBarConfig = createTopBarConfig(), selectedWalletIndex = selectedWalletIndex, wallets = wallets @@ -91,8 +90,7 @@ internal class InitializeWalletsTransformer( title = name, additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = this), imageResId = walletImageResolver.resolve(userWallet = this), - onRenameClick = clickIntents::onRenameBeforeConfirmationClick, - onDeleteClick = clickIntents::onDeleteBeforeConfirmationClick, + dropDownItems = persistentListOf(), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/OpenBottomSheetTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/OpenBottomSheetTransformer.kt index 46ffbf9066..1d3bcc9423 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/OpenBottomSheetTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/OpenBottomSheetTransformer.kt @@ -31,6 +31,9 @@ internal class OpenBottomSheetTransformer( is WalletState.Visa.Locked -> prevState.copy( bottomSheetConfig = updateConfig(), ) + is WalletState.Visa.AccessTokenLocked -> prevState.copy( + bottomSheetConfig = updateConfig(), + ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ReinitializeWalletTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ReinitializeWalletTransformer.kt index 45e0d62979..ceecfc9cfc 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ReinitializeWalletTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ReinitializeWalletTransformer.kt @@ -5,7 +5,7 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import kotlinx.collections.immutable.toImmutableList diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/RenameWalletsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/RenameWalletsTransformer.kt index 9dd8ccb8f9..d9141b8c48 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/RenameWalletsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/RenameWalletsTransformer.kt @@ -44,6 +44,7 @@ internal class RenameWalletsTransformer( is WalletState.MultiCurrency.Locked, is WalletState.SingleCurrency.Locked, is WalletState.Visa.Locked, + is WalletState.Visa.AccessTokenLocked, -> { Timber.e("Impossible to rename wallet in locked state") prevState diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetBalancesAndLimitsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetBalancesAndLimitsTransformer.kt index 9fcfe012b5..2655da4ba9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetBalancesAndLimitsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetBalancesAndLimitsTransformer.kt @@ -7,13 +7,14 @@ import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.format import com.tangem.domain.common.util.getCardsCount +import com.tangem.domain.visa.exception.RefreshTokenExpiredException import com.tangem.domain.visa.model.VisaCurrency import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBlockState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.utils.extensions.isZero import org.joda.time.DateTime import org.joda.time.Days @@ -29,6 +30,10 @@ internal class SetBalancesAndLimitsTransformer( override fun transformTyped(prevState: WalletState.Visa.Content): WalletState { val visaCurrency = maybeVisaCurrency.getOrElse { + if (it is RefreshTokenExpiredException) { + return getRefreshTokenExpiredState(prevState) + } + return prevState.copy( walletCardState = getErrorWalletCardState(prevState.walletCardState), depositButtonState = prevState.depositButtonState.copy(isEnabled = false), @@ -58,8 +63,7 @@ internal class SetBalancesAndLimitsTransformer( id = id, title = title, imageResId = imageResId, - onRenameClick = onRenameClick, - onDeleteClick = onDeleteClick, + dropDownItems = dropDownItems, ) } } @@ -71,8 +75,7 @@ internal class SetBalancesAndLimitsTransformer( title = title, additionalInfo = createAdditionalInfo(visaCurrency), imageResId = imageResId, - onRenameClick = onRenameClick, - onDeleteClick = onDeleteClick, + dropDownItems = dropDownItems, balance = visaCurrency.balances.available.format { crypto(visaCurrency.symbol, visaCurrency.decimals) }, @@ -102,4 +105,14 @@ internal class SetBalancesAndLimitsTransformer( return WalletAdditionalInfo(hideable = true, infoContent) } + + private fun getRefreshTokenExpiredState(prevState: WalletState.Visa.Content): WalletState { + return WalletState.Visa.AccessTokenLocked( + walletCardState = prevState.walletCardState, + buttons = prevState.buttons, + bottomSheetConfig = prevState.bottomSheetConfig, + onExploreClick = clickIntents::onExploreClick, + onUnlockVisaAccessNotificationClick = clickIntents::onUnlockVisaAccessClick, + ) + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetCryptoCurrencyActionsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetCryptoCurrencyActionsTransformer.kt index 136fb6ffa8..0853da92e3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetCryptoCurrencyActionsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetCryptoCurrencyActionsTransformer.kt @@ -6,7 +6,7 @@ import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.toPersistentList import timber.log.Timber diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetExpressStatusesTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetExpressStatusesTransformer.kt index 98058d6c2e..beccf302a6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetExpressStatusesTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetExpressStatusesTransformer.kt @@ -10,7 +10,7 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.SingleWalletOnrampTransactionConverter -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import kotlinx.collections.immutable.toPersistentList import timber.log.Timber diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetRefreshStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetRefreshStateTransformer.kt index 9977790553..1465ccc33a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetRefreshStateTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetRefreshStateTransformer.kt @@ -2,6 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBlockState +import com.tangem.feature.wallet.presentation.wallet.state.model.DepositButtonState import com.tangem.feature.wallet.presentation.wallet.state.model.* import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.mutate @@ -35,6 +37,7 @@ internal class SetRefreshStateTransformer( is WalletState.MultiCurrency.Locked, is WalletState.SingleCurrency.Locked, is WalletState.Visa.Locked, + is WalletState.Visa.AccessTokenLocked, -> prevState } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListErrorTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListErrorTransformer.kt index 46073ee11f..009caf2327 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListErrorTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListErrorTransformer.kt @@ -61,8 +61,7 @@ internal class SetTokenListErrorTransformer( title = title, additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = selectedWallet), imageResId = imageResId, - onRenameClick = onRenameClick, - onDeleteClick = onDeleteClick, + dropDownItems = dropDownItems, balance = BigDecimal.ZERO.format { fiat(fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol) }, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt index 86d40d6d13..ed47e5d973 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt @@ -9,7 +9,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensLis import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCardStateConverter import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TokenListStateConverter import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import timber.log.Timber internal class SetTokenListTransformer( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountErrorTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountErrorTransformer.kt index 9d97991b83..812449f1db 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountErrorTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountErrorTransformer.kt @@ -7,7 +7,7 @@ import com.tangem.domain.txhistory.models.TxHistoryStateError import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TxHistoryItemStateConverter -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import kotlinx.collections.immutable.toImmutableList import timber.log.Timber @@ -33,6 +33,7 @@ internal class SetTxHistoryCountErrorTransformer( is WalletState.Visa.Content -> prevState.copy(txHistoryState = createErrorState()) is WalletState.SingleCurrency.Locked, is WalletState.Visa.Locked, + is WalletState.Visa.AccessTokenLocked, -> { Timber.w("Impossible to load transactions history for locked wallet") prevState diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountTransformer.kt index f61b4961fe..d42259d5a3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountTransformer.kt @@ -5,7 +5,7 @@ import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import timber.log.Timber @@ -26,6 +26,7 @@ internal class SetTxHistoryCountTransformer( ) is WalletState.SingleCurrency.Locked, is WalletState.Visa.Locked, + is WalletState.Visa.AccessTokenLocked, -> { Timber.w("Impossible to load transactions history for locked wallet") prevState diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryItemsErrorTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryItemsErrorTransformer.kt index 5eda4b196e..53c2a25d9d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryItemsErrorTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryItemsErrorTransformer.kt @@ -2,9 +2,10 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.domain.txhistory.models.TxHistoryListError +import com.tangem.domain.visa.exception.RefreshTokenExpiredException import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import timber.log.Timber internal class SetTxHistoryItemsErrorTransformer( @@ -16,9 +17,10 @@ internal class SetTxHistoryItemsErrorTransformer( override fun transform(prevState: WalletState): WalletState { return when (prevState) { is WalletState.SingleCurrency.Content -> prevState.copy(txHistoryState = createErrorState()) - is WalletState.Visa.Content -> prevState.copy(txHistoryState = createErrorState()) + is WalletState.Visa.Content -> transformVisaContent(prevState) is WalletState.SingleCurrency.Locked, is WalletState.Visa.Locked, + is WalletState.Visa.AccessTokenLocked, -> { Timber.w("Impossible to load transactions history for locked wallet") prevState @@ -30,6 +32,20 @@ internal class SetTxHistoryItemsErrorTransformer( } } + private fun transformVisaContent(prevState: WalletState.Visa.Content): WalletState { + return if (error.cause is RefreshTokenExpiredException) { + WalletState.Visa.AccessTokenLocked( + walletCardState = prevState.walletCardState, + buttons = prevState.buttons, + bottomSheetConfig = prevState.bottomSheetConfig, + onExploreClick = clickIntents::onExploreClick, + onUnlockVisaAccessNotificationClick = clickIntents::onUnlockVisaAccessClick, + ) + } else { + prevState.copy(txHistoryState = createErrorState()) + } + } + private fun createErrorState(): TxHistoryState.Error = when (error) { is TxHistoryListError.DataError -> { TxHistoryState.Error( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryItemsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryItemsTransformer.kt index 09c90b2841..4b43c69916 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryItemsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryItemsTransformer.kt @@ -6,7 +6,7 @@ import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TxHistoryItemFlowConverter -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import kotlinx.coroutines.flow.Flow import timber.log.Timber @@ -26,6 +26,7 @@ internal class SetTxHistoryItemsTransformer( ) is WalletState.SingleCurrency.Locked, is WalletState.Visa.Locked, + is WalletState.Visa.AccessTokenLocked, -> { Timber.w("Impossible to load transactions history for locked wallet") prevState diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetWalletCardDropDownItemsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetWalletCardDropDownItemsTransformer.kt new file mode 100644 index 0000000000..4f10b40320 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetWalletCardDropDownItemsTransformer.kt @@ -0,0 +1,80 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers + +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDropDownItems +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.child.wallet.model.intents.WalletCardClickIntents +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList + +internal class SetWalletCardDropDownItemsTransformer( + private val dropdownEnabled: Boolean, + private val clickIntents: WalletCardClickIntents, +) : WalletScreenStateTransformer { + override fun transform(prevState: WalletScreenState): WalletScreenState { + return prevState.copy(wallets = prevState.wallets.map(::transformWalletState).toImmutableList()) + } + + private fun transformWalletState(prevState: WalletState): WalletState { + return when (prevState) { + is WalletState.MultiCurrency.Content -> prevState.copy( + walletCardState = prevState.walletCardState.copySealed( + dropDownItems = constructDropDownItems(prevState.walletCardState.id), + ), + ) + is WalletState.SingleCurrency.Content -> prevState.copy( + walletCardState = prevState.walletCardState.copySealed( + dropDownItems = constructDropDownItems(prevState.walletCardState.id), + ), + ) + is WalletState.Visa.Content -> prevState.copy( + walletCardState = prevState.walletCardState.copySealed( + dropDownItems = constructDropDownItems(prevState.walletCardState.id), + ), + ) + is WalletState.MultiCurrency.Locked -> prevState.copy( + walletCardState = prevState.walletCardState.copySealed( + dropDownItems = constructDropDownItems(prevState.walletCardState.id), + ), + ) + is WalletState.SingleCurrency.Locked -> prevState.copy( + walletCardState = prevState.walletCardState.copySealed( + dropDownItems = constructDropDownItems(prevState.walletCardState.id), + ), + ) + is WalletState.Visa.Locked -> prevState.copy( + walletCardState = prevState.walletCardState.copySealed( + dropDownItems = constructDropDownItems(prevState.walletCardState.id), + ), + ) + is WalletState.Visa.AccessTokenLocked -> prevState.copy( + walletCardState = prevState.walletCardState.copySealed( + dropDownItems = constructDropDownItems(prevState.walletCardState.id), + ), + ) + } + } + + private fun constructDropDownItems(userWalletId: UserWalletId): ImmutableList { + return if (dropdownEnabled) { + persistentListOf( + WalletDropDownItems( + text = resourceReference(id = R.string.common_rename), + icon = R.drawable.ic_edit_24, + onClick = { clickIntents.onRenameBeforeConfirmationClick(userWalletId) }, + ), + WalletDropDownItems( + text = resourceReference(id = R.string.common_delete), + icon = R.drawable.ic_trash_24, + onClick = { clickIntents.onDeleteBeforeConfirmationClick(userWalletId) }, + ), + ) + } else { + persistentListOf() + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetWarningsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetWarningsTransformer.kt index 7d7246a01e..f47eaa2356 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetWarningsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetWarningsTransformer.kt @@ -19,6 +19,7 @@ internal class SetWarningsTransformer( is WalletState.MultiCurrency.Locked, is WalletState.SingleCurrency.Locked, is WalletState.Visa.Locked, + is WalletState.Visa.AccessTokenLocked, -> { Timber.w("Impossible to update notifications for locked wallet") prevState diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UnlockWalletTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UnlockWalletTransformer.kt index 8d4c837f82..61cb2c07b4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UnlockWalletTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UnlockWalletTransformer.kt @@ -6,7 +6,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import kotlinx.collections.immutable.toImmutableList import timber.log.Timber @@ -50,8 +50,9 @@ internal class UnlockWalletTransformer( is WalletState.MultiCurrency.Content, is WalletState.SingleCurrency.Content, is WalletState.Visa.Content, + is WalletState.Visa.AccessTokenLocked, -> { - Timber.e("Impossible to unlock wallet with content state") + Timber.e("Impossible to unlock wallet with not locked state") prevState } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateWalletCardsCountTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateWalletCardsCountTransformer.kt index f350399697..55634425fa 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateWalletCardsCountTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateWalletCardsCountTransformer.kt @@ -27,6 +27,7 @@ internal class UpdateWalletCardsCountTransformer( is WalletState.MultiCurrency.Locked, is WalletState.SingleCurrency.Locked, is WalletState.Visa.Locked, + is WalletState.Visa.AccessTokenLocked, -> { Timber.e("Impossible to update wallet cards count for locked wallet") prevState diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/BalancesAndLimitsBottomSheetConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/BalancesAndLimitsBottomSheetConverter.kt index dc965b29e2..ffb4722637 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/BalancesAndLimitsBottomSheetConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/BalancesAndLimitsBottomSheetConverter.kt @@ -27,7 +27,6 @@ internal class BalancesAndLimitsBottomSheetConverter( availableBalance = value.balances.available.let(::formatAmount), blockedBalance = value.balances.blocked.let(::formatAmount), debit = value.balances.debt.let(::formatAmount), - pending = value.balances.pendingRefund.let(::formatAmount), amlVerified = value.balances.verified.let(::formatAmount), onInfoClick = this::showBalanceInfo, ), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCardStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCardStateConverter.kt index ba2ce3318c..252f77384f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCardStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCardStateConverter.kt @@ -32,8 +32,7 @@ internal class MultiWalletCardStateConverter( title = title, additionalInfo = additionalInfo, imageResId = imageResId, - onRenameClick = onRenameClick, - onDeleteClick = onDeleteClick, + dropDownItems = dropDownItems, ) } @@ -43,8 +42,7 @@ internal class MultiWalletCardStateConverter( title = title, additionalInfo = additionalInfo, imageResId = imageResId, - onDeleteClick = onDeleteClick, - onRenameClick = onRenameClick, + dropDownItems = dropDownItems, ) } @@ -54,8 +52,7 @@ internal class MultiWalletCardStateConverter( title = title, additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = selectedWallet), imageResId = imageResId, - onRenameClick = onRenameClick, - onDeleteClick = onDeleteClick, + dropDownItems = dropDownItems, balance = fiatBalance.amount.format { fiat(fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol) }, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt index 0ff004ae48..d00bb304d0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt @@ -9,7 +9,7 @@ import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.state.model.TokenActionButtonConfig -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletCurrencyActionsClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletCurrencyActionsClickIntents import com.tangem.utils.converter.Converter import com.tangem.utils.isNullOrZero import kotlinx.collections.immutable.ImmutableList diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletCardStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletCardStateConverter.kt index 48aaa65b0e..c23ed71b36 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletCardStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletCardStateConverter.kt @@ -39,8 +39,7 @@ internal class SingleWalletCardStateConverter( id = id, title = title, imageResId = imageResId, - onRenameClick = onRenameClick, - onDeleteClick = onDeleteClick, + dropDownItems = dropDownItems, ) } @@ -49,8 +48,7 @@ internal class SingleWalletCardStateConverter( id = id, title = title, imageResId = imageResId, - onRenameClick = onRenameClick, - onDeleteClick = onDeleteClick, + dropDownItems = dropDownItems, ) } @@ -63,8 +61,7 @@ internal class SingleWalletCardStateConverter( currencyAmount = status.amount, ), imageResId = imageResId, - onRenameClick = onRenameClick, - onDeleteClick = onDeleteClick, + dropDownItems = dropDownItems, balance = formatFiatAmount(status = status, appCurrency = appCurrency), cardCount = selectedWallet.getCardsCount(), isZeroBalance = status.fiatAmount?.isZero(), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletOnrampTransactionConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletOnrampTransactionConverter.kt index a5a0ee99b1..c506948924 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletOnrampTransactionConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletOnrampTransactionConverter.kt @@ -26,7 +26,7 @@ import com.tangem.feature.wallet.impl.R import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.persistentListOf diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt index c3d4c02d9c..ac8b4feb69 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt @@ -13,7 +13,7 @@ import com.tangem.domain.tokens.model.TotalFiatBalance import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.mutate diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt index b24e902bfa..a7ff9b5ba3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt @@ -5,7 +5,7 @@ import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.utils.converter.Converter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt index a85ca99eab..ecd092b477 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt @@ -12,7 +12,7 @@ import com.tangem.core.ui.utils.toTimeFormat import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryItem.* import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.utils.StringsSigns.MINUS import com.tangem.utils.StringsSigns.PLUS import com.tangem.utils.converter.Converter diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxDetailsBottomSheetConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxDetailsBottomSheetConverter.kt index e655973c16..dee6043f7c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxDetailsBottomSheetConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxDetailsBottomSheetConverter.kt @@ -8,7 +8,7 @@ import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.domain.visa.model.VisaCurrency import com.tangem.domain.visa.model.VisaTxDetails import com.tangem.feature.wallet.presentation.wallet.state.model.VisaTxDetailsBottomSheetConfig -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.VisaWalletIntents +import com.tangem.feature.wallet.child.wallet.model.intents.VisaWalletIntents import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toImmutableList import org.joda.time.DateTimeZone diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxHistoryItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxHistoryItemStateConverter.kt index fe50fdf537..62d00e4c3c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxHistoryItemStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxHistoryItemStateConverter.kt @@ -10,7 +10,7 @@ import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.domain.visa.model.VisaCurrency import com.tangem.domain.visa.model.VisaTxHistoryItem import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.VisaWalletIntents +import com.tangem.feature.wallet.child.wallet.model.intents.VisaWalletIntents import com.tangem.utils.StringsSigns import com.tangem.utils.converter.Converter import org.joda.time.DateTimeZone diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt index 601894c802..6223bc1d05 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt @@ -8,7 +8,7 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver import com.tangem.feature.wallet.presentation.wallet.state.model.* -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf @@ -90,8 +90,7 @@ internal class WalletLoadingStateFactory( title = name, additionalInfo = if (isMultiCurrency) WalletAdditionalInfoFactory.resolve(wallet = this) else null, imageResId = walletImageResolver.resolve(userWallet = this), - onRenameClick = clickIntents::onRenameBeforeConfirmationClick, - onDeleteClick = clickIntents::onDeleteBeforeConfirmationClick, + dropDownItems = persistentListOf(), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index 5ae57f60ab..34389c69a8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -11,12 +11,12 @@ import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListErrorTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListTransformer -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn import kotlinx.coroutines.CoroutineScope diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index 63650daf64..260517ff46 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -11,11 +11,11 @@ import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TotalFiatBalance import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.coroutines.CoroutineScope @Suppress("LongParameterList") diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletWarningsSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletWarningsSubscriber.kt index f4a628c17a..995348683f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletWarningsSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletWarningsSubscriber.kt @@ -1,13 +1,13 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsSingleEventSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetWarningsTransformer -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletButtonsSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletButtonsSubscriber.kt index 4751fed097..3404216570 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletButtonsSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletButtonsSubscriber.kt @@ -4,10 +4,10 @@ import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.domain.collectLatest import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetCryptoCurrencyActionsTransformer -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletExpressStatusesSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletExpressStatusesSubscriber.kt index 37eb922c67..0e4df60030 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletExpressStatusesSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletExpressStatusesSubscriber.kt @@ -12,9 +12,9 @@ import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetExpressStatusesTransformer -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* import timber.log.Timber diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletNotificationsSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletNotificationsSubscriber.kt index a1ef1d4ee6..e08eac5427 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletNotificationsSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletNotificationsSubscriber.kt @@ -1,12 +1,12 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetSingleWalletWarningsFactory import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetWarningsTransformer -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt index 3cb19dbc3a..e52c903a67 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt @@ -7,11 +7,11 @@ import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.coroutines.CoroutineScope @Suppress("LongParameterList") diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt index 7561ea68c5..ad67739b99 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt @@ -13,6 +13,7 @@ import com.tangem.domain.txhistory.models.TxHistoryStateError import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.domain.collectLatest import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryCountErrorTransformer @@ -20,7 +21,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHis import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryItemsErrorTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryItemsTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TxHistoryItemStateConverter -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/VisaWalletSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/VisaWalletSubscriber.kt index 9f6c2b8eb2..239ee8c80d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/VisaWalletSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/VisaWalletSubscriber.kt @@ -11,13 +11,13 @@ import com.tangem.domain.visa.GetVisaTxHistoryUseCase import com.tangem.domain.visa.model.VisaCurrency import com.tangem.domain.visa.model.VisaTxHistoryItem import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetBalancesAndLimitsTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryCountTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryItemsErrorTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryItemsTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.VisaTxHistoryItemStateConverter -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/WalletDropDownItemsSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/WalletDropDownItemsSubscriber.kt new file mode 100644 index 0000000000..01df7c3340 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/WalletDropDownItemsSubscriber.kt @@ -0,0 +1,30 @@ +package com.tangem.feature.wallet.presentation.wallet.subscribers + +import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetWalletCardDropDownItemsTransformer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.* + +internal class WalletDropDownItemsSubscriber( + private val stateHolder: WalletStateController, + private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase, + private val clickIntents: WalletClickIntents, +) : WalletSubscriber() { + override fun create(coroutineScope: CoroutineScope): Flow<*> { + return flow { + shouldSaveUserWalletsUseCase.invoke() + .distinctUntilChanged() + .onEach { + stateHolder.update( + SetWalletCardDropDownItemsTransformer( + dropdownEnabled = it, + clickIntents = clickIntents, + ), + ) + } + .launchIn(coroutineScope) + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 414d14cd71..69c2f73c77 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -67,6 +67,10 @@ import com.tangem.core.ui.test.TestTags import com.tangem.core.ui.utils.lineTo import com.tangem.core.ui.utils.moveTo import com.tangem.core.ui.utils.toPx +import com.tangem.feature.wallet.presentation.wallet.state.model.ActionsBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.VisaTxDetailsBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.walletScreenState import com.tangem.feature.wallet.presentation.wallet.state.model.* @@ -92,8 +96,6 @@ import kotlin.math.roundToInt @Composable internal fun WalletScreen(state: WalletScreenState, marketsEntryComponent: MarketsEntryComponent) { - BackHandler(onBack = state.onBackClick) - // It means that screen is still initializing if (state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX) return diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletNFTItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletNFTItem.kt new file mode 100644 index 0000000000..d31a09e214 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletNFTItem.kt @@ -0,0 +1,453 @@ +package com.tangem.feature.wallet.presentation.wallet.ui.components + +import android.content.res.Configuration +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import coil.compose.SubcomposeAsyncImage +import com.tangem.core.ui.R +import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.components.TextShimmer +import com.tangem.core.ui.components.text.applyBladeBrush +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNFTItemUM +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNFTItemUM.Content.CollectionPreview +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +@Composable +internal fun WalletNFTItem(state: WalletNFTItemUM, modifier: Modifier = Modifier, onClick: () -> Unit = { }) { + when (state) { + is WalletNFTItemUM.Hidden -> Unit + is WalletNFTItemUM.Empty -> WalletNFTItemEmpty( + modifier = modifier, + onClick = onClick, + ) + is WalletNFTItemUM.Failed -> WalletNFTItemFailed(modifier = modifier) + is WalletNFTItemUM.Loading -> WalletNFTItemLoading(modifier = modifier) + + is WalletNFTItemUM.Content -> WalletNFTItemContent( + state = state, + onClick = onClick, + modifier = modifier, + ) + } +} + +@Composable +private fun WalletNFTItemEmpty(onClick: () -> Unit, modifier: Modifier = Modifier) { + RowContentContainer( + modifier = modifier, + icon = { + Image( + modifier = Modifier, + painter = painterResource(R.drawable.ic_nft_empty_36), + contentDescription = null, + ) + }, + text = { + Text( + text = stringResourceSafe(R.string.nft_wallet_title), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.primary1, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + text = stringResourceSafe(R.string.nft_wallet_receive_nft), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + }, + action = { + ChevronArrow() + }, + onClick = onClick, + ) +} + +@Composable +private fun WalletNFTItemContent(state: WalletNFTItemUM.Content, onClick: () -> Unit, modifier: Modifier = Modifier) { + RowContentContainer( + modifier = modifier, + icon = { + CollectionsPreviews( + previews = state.previews, + ) + }, + text = { + Text( + text = stringResourceSafe(R.string.nft_wallet_title), + style = TangemTheme.typography.subtitle2.applyBladeBrush( + isEnabled = state.isFlickering, + textColor = TangemTheme.colors.text.primary1, + ), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + text = stringResourceSafe( + id = R.string.nft_wallet_count, + state.assetsCount, + state.collectionsCount, + ), + style = TangemTheme.typography.caption2.applyBladeBrush( + isEnabled = state.isFlickering, + textColor = TangemTheme.colors.text.tertiary, + ), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + }, + action = { + ChevronArrow() + }, + onClick = onClick, + ) +} + +@Composable +private fun WalletNFTItemFailed(modifier: Modifier = Modifier) { + RowContentContainer( + modifier = modifier, + icon = { + CollectionsPreviewsPlaceholder() + }, + text = { + Text( + text = stringResourceSafe(R.string.nft_wallet_title), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + text = stringResourceSafe(R.string.nft_wallet_unable_to_load), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + }, + enabled = false, + ) +} + +@Composable +private fun WalletNFTItemLoading(modifier: Modifier = Modifier) { + RowContentContainer( + modifier = modifier, + icon = { + CollectionsPreviewsPlaceholder() + }, + text = { + TextShimmer( + modifier = Modifier.width(TangemTheme.dimens.size110), + style = TangemTheme.typography.caption2, + textSizeHeight = true, + ) + TextShimmer( + modifier = Modifier + .width(TangemTheme.dimens.size80) + .padding(top = TangemTheme.dimens.spacing4), + style = TangemTheme.typography.caption2, + textSizeHeight = true, + ) + }, + enabled = false, + ) +} + +@Composable +private fun CollectionsPreviewsPlaceholder(modifier: Modifier = Modifier) { + Box( + modifier = modifier + .size(TangemTheme.dimens.size36) + .clip(RoundedCornerShape(TangemTheme.dimens.radius8)) + .background(TangemTheme.colors.field.primary), + ) +} + +@Composable +@Suppress("MagicNumber") +private fun BoxScope.CollectionsPreviews(previews: ImmutableList, modifier: Modifier = Modifier) { + val modifiers = when (previews.size) { + 1 -> previews1Modifiers() + 2 -> previews2Modifiers() + 3 -> previews3Modifiers() + else -> previews4Modifiers() + } + Box( + modifier = modifier + .size(TangemTheme.dimens.size36), + ) { + previews.take(modifiers.size).forEachIndexed { index, s -> + val modifier = modifiers[index] + when (s) { + is CollectionPreview.Image -> { + SubcomposeAsyncImage( + modifier = modifier, + model = s, + loading = { + RectangleShimmer(radius = 0.dp) + }, + error = { + Box( + modifier = Modifier.background(TangemTheme.colors.field.primary), + ) + }, + contentDescription = null, + ) + } + is CollectionPreview.More -> { + Image( + modifier = modifier + .background(TangemTheme.colors.stroke.primary), + painter = painterResource(R.drawable.ic_nft_preview_more_16), + contentDescription = null, + ) + } + } + } + } +} + +@Composable +private fun BoxScope.previews1Modifiers(): List = listOf( + Modifier + .size(TangemTheme.dimens.size36) + .clip(RoundedCornerShape(TangemTheme.dimens.radius6)), +) + +@Composable +private fun BoxScope.previews2Modifiers(): List = listOf( + Modifier + .size(TangemTheme.dimens.size22) + .padding( + top = TangemTheme.dimens.spacing1, + ) + .clip(RoundedCornerShape(TangemTheme.dimens.radius6)) + .align(Alignment.TopStart), + Modifier + .zIndex(1f) + .clip(RoundedCornerShape(TangemTheme.dimens.radius6)) + .background(TangemTheme.colors.background.primary) + .padding( + top = TangemTheme.dimens.spacing2, + start = TangemTheme.dimens.spacing2, + ) + .size(TangemTheme.dimens.size22) + .clip(RoundedCornerShape(TangemTheme.dimens.radius6)) + .align(Alignment.BottomEnd), +) + +@Composable +private fun BoxScope.previews3Modifiers(): List = listOf( + Modifier + .padding( + top = TangemTheme.dimens.spacing1, + bottom = TangemTheme.dimens.spacing1, + ) + .size(TangemTheme.dimens.size16) + .clip(RoundedCornerShape(5.dp)) + .align(Alignment.TopStart), + Modifier + .zIndex(1f) + .padding( + top = TangemTheme.dimens.radius4, + ) + .clip(RoundedCornerShape(TangemTheme.dimens.radius6)) + .background(TangemTheme.colors.background.primary) + .padding( + start = TangemTheme.dimens.spacing2, + top = TangemTheme.dimens.spacing2, + end = TangemTheme.dimens.spacing1, + bottom = TangemTheme.dimens.spacing2, + ) + .size(TangemTheme.dimens.size18) + .clip(RoundedCornerShape(TangemTheme.dimens.radius6)) + .align(Alignment.TopEnd), + Modifier + .padding( + start = TangemTheme.dimens.spacing6, + bottom = TangemTheme.dimens.spacing1, + ) + .clip(RoundedCornerShape(TangemTheme.dimens.radius4)) + .size(TangemTheme.dimens.size14) + .align(Alignment.BottomStart), +) + +@Composable +private fun BoxScope.previews4Modifiers(): List = listOf( + Modifier + .clip(RoundedCornerShape(TangemTheme.dimens.radius4)) + .size(TangemTheme.dimens.size16) + .align(Alignment.TopStart), + Modifier + .clip(RoundedCornerShape(TangemTheme.dimens.radius4)) + .size(TangemTheme.dimens.size16) + .align(Alignment.TopEnd), + Modifier + .clip(RoundedCornerShape(TangemTheme.dimens.radius4)) + .size(TangemTheme.dimens.size16) + .align(Alignment.BottomStart), + Modifier + .clip(RoundedCornerShape(TangemTheme.dimens.radius4)) + .size(TangemTheme.dimens.size16) + .align(Alignment.BottomEnd), +) + +@Composable +private fun ChevronArrow(modifier: Modifier = Modifier) { + Icon( + modifier = modifier, + painter = painterResource(R.drawable.ic_chevron_right_24), + contentDescription = null, + tint = TangemTheme.colors.icon.informative, + ) +} + +@Composable +private fun RowContentContainer( + modifier: Modifier = Modifier, + onClick: () -> Unit = {}, + enabled: Boolean = true, + icon: @Composable BoxScope.() -> Unit, + text: @Composable ColumnScope.() -> Unit, + action: @Composable BoxScope.() -> Unit = {}, +) { + Card( + modifier = modifier.fillMaxWidth(), + shape = RoundedCornerShape(TangemTheme.dimens.radius16), + colors = CardDefaults.cardColors( + containerColor = TangemTheme.colors.background.primary, + contentColor = TangemTheme.colors.text.primary1, + disabledContainerColor = TangemTheme.colors.background.primary, + disabledContentColor = TangemTheme.colors.text.primary1, + ), + onClick = onClick, + enabled = enabled, + ) { + Row( + modifier = Modifier + .padding( + horizontal = TangemTheme.dimens.spacing12, + vertical = TangemTheme.dimens.spacing16, + ), + verticalAlignment = Alignment.CenterVertically, + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + contentAlignment = Alignment.Center, + content = icon, + ) + Column( + modifier = Modifier + .padding( + horizontal = TangemTheme.dimens.spacing12, + ) + .weight(1f), + horizontalAlignment = Alignment.Start, + content = text, + ) + Box( + modifier = Modifier.heightIn(min = TangemTheme.dimens.size24), + contentAlignment = Alignment.CenterEnd, + content = action, + ) + } + } + } +} + +@Preview(widthDp = 360) +@Preview(widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_WalletNFTItem_InLight(@PreviewParameter(WalletNFTItemProvider::class) state: WalletNFTItemUM) { + TangemThemePreview { + WalletNFTItem( + state = state, + onClick = {}, + ) + } +} + +private class WalletNFTItemProvider : CollectionPreviewParameterProvider( + collection = listOf( + WalletNFTItemUM.Empty, + WalletNFTItemUM.Loading, + WalletNFTItemUM.Failed, + WalletNFTItemUM.Content( + previews = persistentListOf( + CollectionPreview.Image("img1"), + ), + assetsCount = 125, + collectionsCount = 11, + isFlickering = true, + ), + WalletNFTItemUM.Content( + previews = persistentListOf( + CollectionPreview.Image("img1"), + CollectionPreview.Image("img2"), + ), + assetsCount = 125, + collectionsCount = 11, + isFlickering = false, + ), + WalletNFTItemUM.Content( + previews = persistentListOf( + CollectionPreview.Image("img1"), + CollectionPreview.Image("img2"), + CollectionPreview.Image("img3"), + ), + assetsCount = 125, + collectionsCount = 11, + isFlickering = false, + ), + WalletNFTItemUM.Content( + previews = persistentListOf( + CollectionPreview.Image("img1"), + CollectionPreview.Image("img2"), + CollectionPreview.Image("img3"), + CollectionPreview.Image("img4"), + ), + assetsCount = 125, + collectionsCount = 11, + isFlickering = false, + ), + WalletNFTItemUM.Content( + previews = persistentListOf( + CollectionPreview.Image("img1"), + CollectionPreview.Image("img2"), + CollectionPreview.Image("img3"), + CollectionPreview.More, + ), + assetsCount = 125, + collectionsCount = 11, + isFlickering = true, + ), + ), +) \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt index 48824cb059..a11dba3be1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt @@ -2,7 +2,6 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.common import android.content.res.Configuration import androidx.annotation.DrawableRes -import androidx.annotation.StringRes import androidx.compose.animation.* import androidx.compose.animation.core.tween import androidx.compose.foundation.Image @@ -14,9 +13,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Delete -import androidx.compose.material.icons.outlined.Edit import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -30,6 +26,7 @@ import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -48,13 +45,14 @@ import com.tangem.core.ui.components.text.applyBladeBrush import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.orMaskWithStars import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemDimens import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.common.WalletPreviewData import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDropDownItems +import com.tangem.feature.wallet.presentation.wallet.ui.components.fastForEach +import kotlinx.collections.immutable.ImmutableList private const val HALF_OF_ITEM_WIDTH = 0.5 @@ -71,8 +69,7 @@ private const val HALF_OF_ITEM_WIDTH = 0.5 internal fun WalletCard(state: WalletCardState, isBalanceHidden: Boolean, modifier: Modifier = Modifier) { @Suppress("DestructuringDeclarationWithTooManyEntries") CardContainer( - onDeleteClick = { state.onDeleteClick(state.id) }, - onRenameClick = { state.onRenameClick(state.id) }, + dropDownItems = state.dropDownItems, isLockedState = state is WalletCardState.LockedContent, modifier = modifier, ) { itemSize -> @@ -148,8 +145,7 @@ internal fun WalletCard(state: WalletCardState, isBalanceHidden: Boolean, modifi @Composable private fun CardContainer( - onDeleteClick: () -> Unit, - onRenameClick: () -> Unit, + dropDownItems: ImmutableList, isLockedState: Boolean, modifier: Modifier = Modifier, content: @Composable (ConstraintLayoutScope.(IntSize) -> Unit), @@ -167,7 +163,7 @@ private fun CardContainer( .defaultMinSize(minHeight = TangemTheme.dimens.size108) .onSizeChanged { itemSize = it } .then( - if (isLockedState) { + if (isLockedState || dropDownItems.isEmpty()) { Modifier } else { Modifier @@ -210,8 +206,7 @@ private fun CardContainer( pressOffset = pressOffset, itemHeight = itemHeight, onDismissRequest = { isMenuVisible = false }, - onShowRenameWalletDialogClick = onRenameClick, - onDeleteClick = onDeleteClick, + dropDownItems = dropDownItems, ) } @@ -222,8 +217,7 @@ private fun ManageWalletContextMenu( pressOffset: DpOffset, itemHeight: Dp, onDismissRequest: () -> Unit, - onShowRenameWalletDialogClick: () -> Unit, - onDeleteClick: () -> Unit, + dropDownItems: ImmutableList, ) { DropdownMenu( expanded = isMenuVisible, @@ -231,29 +225,23 @@ private fun ManageWalletContextMenu( modifier = Modifier.background(color = TangemTheme.colors.background.secondary), offset = pressOffset.copy(y = pressOffset.y - itemHeight), ) { - MenuItem( - textResId = R.string.common_rename, - imageVector = Icons.Outlined.Edit, - onClick = { - onDismissRequest() - onShowRenameWalletDialogClick() - }, - ) - MenuItem( - textResId = R.string.common_delete, - imageVector = Icons.Outlined.Delete, - onClick = { - onDismissRequest() - onDeleteClick() - }, - ) + dropDownItems.fastForEach { item -> + MenuItem( + text = item.text, + imageVector = ImageVector.vectorResource(id = item.icon), + onClick = { + onDismissRequest() + item.onClick() + }, + ) + } } } @Composable -private fun MenuItem(@StringRes textResId: Int, imageVector: ImageVector, onClick: () -> Unit) { +private fun MenuItem(text: TextReference, imageVector: ImageVector, onClick: () -> Unit) { DropdownMenuItem( - text = { Text(text = stringResourceSafe(id = textResId), style = TangemTheme.typography.subtitle2) }, + text = { Text(text = text.resolveReference(), style = TangemTheme.typography.subtitle2) }, modifier = Modifier.background(color = TangemTheme.colors.background.secondary), trailingIcon = { Icon(imageVector = imageVector, contentDescription = null) }, onClick = onClick, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/BalancesAndLimitsBottomSheet.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/BalancesAndLimitsBottomSheet.kt index 97dd93b282..d9296dab81 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/BalancesAndLimitsBottomSheet.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/BalancesAndLimitsBottomSheet.kt @@ -78,10 +78,6 @@ private fun BalancesBlock(balances: BalancesAndLimitsBottomSheetConfig.Balance, title = stringReference("Debit"), value = balances.debit, ) - BlockItem( - title = stringReference("Pending refund"), - value = balances.pending, - ) }, description = { InfoButton(onClick = balances.onInfoClick) @@ -180,7 +176,6 @@ private class BalancesAndLimitsBottomSheetParameterProvider : availableBalance = "392.45 USDT", blockedBalance = "36.00 USDT", debit = "00.00 USDT", - pending = "20.99 USDT", amlVerified = "356.45 USDT", onInfoClick = {}, ), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/ScreenLifecycleProvider.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/ScreenLifecycleProvider.kt index f2c721ac87..f9f55bea5a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/ScreenLifecycleProvider.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/ScreenLifecycleProvider.kt @@ -1,23 +1,22 @@ package com.tangem.feature.wallet.presentation.wallet.utils -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner -import dagger.hilt.android.scopes.ViewModelScoped +import com.arkivanov.essenty.lifecycle.Lifecycle +import com.tangem.core.decompose.di.ModelScoped import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject -@ViewModelScoped -internal class ScreenLifecycleProvider @Inject constructor() : DefaultLifecycleObserver { +@ModelScoped +internal class ScreenLifecycleProvider @Inject constructor() : Lifecycle.Callbacks { private val _isBackgroundState = MutableStateFlow(false) val isBackgroundState: StateFlow = _isBackgroundState - override fun onResume(owner: LifecycleOwner) { + override fun onResume() { _isBackgroundState.value = false } - override fun onPause(owner: LifecycleOwner) { + override fun onPause() { _isBackgroundState.value = true } } \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 11c13809b2..03c21fccac 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -6,7 +6,8 @@ androidGradlePlugin = "8.2.2" firebaseCrashlytics = "3.0.1" googleServices = "4.4.1" -kotlin = "1.9.22" +kotlin = "2.1.10" +ksp = "2.1.10-1.0.30" firebasePerf = "1.4.2" # endregion Classpath @@ -26,11 +27,10 @@ androidxWindowManager = "1.3.0" # endregion AndroidX # region Compose -compose-compiler = "1.5.9" -compose-runtime = "1.7.1" -compose-foundation = "1.7.1" -compose-material = "1.7.1" -compose-material3 = "1.3.0" +compose-runtime = "1.7.8" +compose-foundation = "1.7.8" +compose-material = "1.7.8" +compose-material3 = "1.3.1" compose-constraint = "1.0.1" compose-navigation = "2.7.7" compose-accompanist = "0.30.1" @@ -44,14 +44,14 @@ amplitude = "2.36.1" armadillo = "0.9.0" coil = "2.1.0" compose-shimmer = "1.0.3" -coroutine = "1.7.2" +coroutine = "1.7.2" # 1.8+ is not compatible with tangem-sdk desugarJdkLibs = "1.1.5" firebase = "33.7.0" googleMaterialComponent = "1.6.1" googlePlayReview = "2.0.1" googlePlayReviewKtx = "2.0.1" googlePlayServicesWallet = "19.1.0" -hilt = "2.46" +hilt = "2.55" hilt-navigation = "1.0.0" jodatime = "2.12.1" kotlin-immutable-collections = "0.3.5" @@ -69,9 +69,8 @@ timber = "4.7.1" viewBindingDelegate = "1.5.9" xmlShimmer = "1.1.3" zxingQrCode = "3.5.1" -mviCore = "1.3.1" -kotlinSerialization = "1.4.1" -arrow = "1.2.3" +kotlinSerialization = "1.8.0" +arrow = "1.2.4" # 2.0.1 breaks the build reownCore = "1.1.2" reownWeb3 = "1.1.2" prettyLogger = "2.2.0" @@ -81,9 +80,9 @@ mlKit-barcodeScanning = "17.2.0" androidXCamera = "1.3.0" listenableFuture = "1.0" swipeRefreshLayout = "1.1.0" -web3j = "4.10.1" +web3j = "4.12.3-SNAPSHOT" leakcanary = "2.13" -decompose = "2.2.3" +decompose = "3.3.0" room = "2.6.1" markdown = "0.7.2" markdownComposeView = "0.5.4" @@ -121,6 +120,8 @@ google-services = { id = "com.google.gms.google-services", version.ref = "google hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } room = { id = "androidx.room", version.ref = "room" } +kotlin-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } [libraries] # region Classpath @@ -254,7 +255,7 @@ camera-view = { module = "androidx.camera:camera-view", version.ref = "androidXC web3j-core = { module = "org.web3j:core", version.ref = "web3j" } leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" } decompose = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" } -decompose-ext-compose = { module = "com.arkivanov.decompose:extensions-compose-jetpack", version.ref = "decompose" } +decompose-ext-compose = { module = "com.arkivanov.decompose:extensions-compose", version.ref = "decompose" } room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } diff --git a/libs/blockchain-sdk/build.gradle.kts b/libs/blockchain-sdk/build.gradle.kts index 31e9b0408e..3a1001135f 100644 --- a/libs/blockchain-sdk/build.gradle.kts +++ b/libs/blockchain-sdk/build.gradle.kts @@ -5,6 +5,7 @@ plugins { alias(deps.plugins.kotlin.android) alias(deps.plugins.kotlin.kapt) alias(deps.plugins.hilt.android) + alias(deps.plugins.ksp) id("configuration") } @@ -34,7 +35,7 @@ dependencies { implementation(deps.moshi) implementation(deps.moshi.kotlin) implementation(deps.timber) - kaptForObfuscatingVariants(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) kaptForObfuscatingVariants(deps.retrofit.response.type.keeper) // endregion diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt index 866170f4c5..dc2f9b8a81 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt @@ -159,6 +159,10 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "apechain" -> Blockchain.ApeChain "apechain/test" -> Blockchain.ApeChainTestnet "kaspa/test" -> Blockchain.KaspaTestnet + "scroll" -> Blockchain.Scroll + "scroll/test" -> Blockchain.ScrollTestnet + "zklink" -> Blockchain.ZkLinkNova + "zklink/test" -> Blockchain.ZkLinkNovaTestnet else -> null } } @@ -315,6 +319,10 @@ fun Blockchain.toNetworkId(): String { Blockchain.SonicTestnet -> "sonic/test" Blockchain.ApeChain -> "apechain" Blockchain.ApeChainTestnet -> "apechain/test" + Blockchain.Scroll -> "scroll" + Blockchain.ScrollTestnet -> "scroll/test" + Blockchain.ZkLinkNova -> "zklink" + Blockchain.ZkLinkNovaTestnet -> "zklink/test" } } @@ -415,6 +423,8 @@ fun Blockchain.toCoinId(): String { Blockchain.Bitrock, Blockchain.BitrockTestnet -> "bitrock" Blockchain.Sonic, Blockchain.SonicTestnet -> "sonic-3" Blockchain.ApeChain, Blockchain.ApeChainTestnet -> "apecoin" + Blockchain.Scroll, Blockchain.ScrollTestnet -> "scroll" + Blockchain.ZkLinkNova, Blockchain.ZkLinkNovaTestnet -> "zklink" } } diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index d34ef3b532..619324480d 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -18,6 +18,7 @@ import java.math.BigDecimal * Temporary solution for domain specific logic for Blockchain. * Instead of creating repositories and unnecessary and overkill use cases */ +@Suppress("TooManyFunctions") object BlockchainUtils { private const val XRP_X_ADDRESS = 'X' @@ -137,6 +138,18 @@ object BlockchainUtils { } } + fun isIncludeStakingTotalBalance(blockchainId: String): Boolean { + val blockchain = Blockchain.fromId(blockchainId) + + return blockchain != Blockchain.Cardano + } + + fun isSkipAmountEnter(blockchainId: String): Boolean { + val blockchain = Blockchain.fromId(blockchainId) + + return blockchain == Blockchain.Cardano + } + private fun getNetworkStandardName(blockchain: Blockchain): String { return when (blockchain) { Blockchain.Ethereum, Blockchain.EthereumTestnet -> "ERC20" diff --git a/libs/visa/build.gradle.kts b/libs/visa/build.gradle.kts index eb6f922306..c3a3b8674b 100644 --- a/libs/visa/build.gradle.kts +++ b/libs/visa/build.gradle.kts @@ -4,11 +4,18 @@ plugins { alias(deps.plugins.android.library) alias(deps.plugins.kotlin.android) alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.ksp) id("configuration") } android { namespace = "com.tangem.libs.visa" + + packaging { + resources { + excludes += "/META-INF/*" + } + } } dependencies { @@ -24,7 +31,7 @@ dependencies { implementation(deps.okHttp.prettyLogging) implementation(deps.retrofit) implementation(deps.retrofit.moshi) - kaptForObfuscatingVariants(deps.moshi.kotlin.codegen) + ksp(deps.moshi.kotlin.codegen) kaptForObfuscatingVariants(deps.retrofit.response.type.keeper) /** Libs - Other */ diff --git a/libs/visa/src/main/java/com/tangem/lib/visa/ERC20.java b/libs/visa/src/main/java/com/tangem/lib/visa/ERC20.java index fb6048f082..5ea4655792 100644 --- a/libs/visa/src/main/java/com/tangem/lib/visa/ERC20.java +++ b/libs/visa/src/main/java/com/tangem/lib/visa/ERC20.java @@ -1,8 +1,18 @@ package com.tangem.lib.visa; +import io.reactivex.Flowable; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import org.web3j.abi.EventEncoder; import org.web3j.abi.TypeReference; -import org.web3j.abi.datatypes.*; +import org.web3j.abi.datatypes.Address; +import org.web3j.abi.datatypes.Event; +import org.web3j.abi.datatypes.Function; +import org.web3j.abi.datatypes.Type; +import org.web3j.abi.datatypes.Utf8String; import org.web3j.abi.datatypes.generated.Uint256; import org.web3j.abi.datatypes.generated.Uint8; import org.web3j.crypto.Credentials; @@ -17,25 +27,17 @@ import org.web3j.tx.Contract; import org.web3j.tx.TransactionManager; import org.web3j.tx.gas.ContractGasProvider; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import io.reactivex.Flowable; - /** *

Auto generated code. *

Do not modify! *

Please use the web3j command line tools, - * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the - * codegen module to update. + * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the + * codegen module to update. * - *

Generated with web3j version 1.5.2. + *

Generated with web3j version 1.6.1. */ @SuppressWarnings("rawtypes") -class ERC20 extends Contract { +public class ERC20 extends Contract { public static final String BINARY = "Bin file was not provided"; public static final String FUNC_ALLOWANCE = "allowance"; @@ -57,39 +59,40 @@ class ERC20 extends Contract { public static final String FUNC_TRANSFERFROM = "transferFrom"; public static final Event APPROVAL_EVENT = new Event("Approval", - Arrays.asList(new TypeReference

(true) { - }, new TypeReference
(true) { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference
(true) {}, new TypeReference
(true) {}, new TypeReference() {})); + ; public static final Event TRANSFER_EVENT = new Event("Transfer", - Arrays.asList(new TypeReference
(true) { - }, new TypeReference
(true) { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference
(true) {}, new TypeReference
(true) {}, new TypeReference() {})); + ; @Deprecated - protected ERC20(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + protected ERC20(String contractAddress, Web3j web3j, Credentials credentials, + BigInteger gasPrice, BigInteger gasLimit) { super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); } - protected ERC20(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + protected ERC20(String contractAddress, Web3j web3j, Credentials credentials, + ContractGasProvider contractGasProvider) { super(BINARY, contractAddress, web3j, credentials, contractGasProvider); } @Deprecated - protected ERC20(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + protected ERC20(String contractAddress, Web3j web3j, TransactionManager transactionManager, + BigInteger gasPrice, BigInteger gasLimit) { super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); } - protected ERC20(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + protected ERC20(String contractAddress, Web3j web3j, TransactionManager transactionManager, + ContractGasProvider contractGasProvider) { super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); } - public static List getApprovalEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(APPROVAL_EVENT, transactionReceipt); + public static List getApprovalEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(APPROVAL_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { ApprovalEventResponse typedResponse = new ApprovalEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.owner = (String) eventValues.getIndexedValues().get(0).getValue(); @@ -101,7 +104,7 @@ class ERC20 extends Contract { } public static ApprovalEventResponse getApprovalEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(APPROVAL_EVENT, log); + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(APPROVAL_EVENT, log); ApprovalEventResponse typedResponse = new ApprovalEventResponse(); typedResponse.log = log; typedResponse.owner = (String) eventValues.getIndexedValues().get(0).getValue(); @@ -110,20 +113,22 @@ class ERC20 extends Contract { return typedResponse; } - @Deprecated - public static ERC20 load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { - return new ERC20(contractAddress, web3j, credentials, gasPrice, gasLimit); + public Flowable approvalEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getApprovalEventFromLog(log)); } - @Deprecated - public static ERC20 load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { - return new ERC20(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + public Flowable approvalEventFlowable(DefaultBlockParameter startBlock, + DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(APPROVAL_EVENT)); + return approvalEventFlowable(filter); } - public static List getTransferEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(TRANSFER_EVENT, transactionReceipt); + public static List getTransferEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(TRANSFER_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { TransferEventResponse typedResponse = new TransferEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.from = (String) eventValues.getIndexedValues().get(0).getValue(); @@ -135,7 +140,7 @@ class ERC20 extends Contract { } public static TransferEventResponse getTransferEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSFER_EVENT, log); + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSFER_EVENT, log); TransferEventResponse typedResponse = new TransferEventResponse(); typedResponse.log = log; typedResponse.from = (String) eventValues.getIndexedValues().get(0).getValue(); @@ -148,107 +153,107 @@ class ERC20 extends Contract { return web3j.ethLogFlowable(filter).map(log -> getTransferEventFromLog(log)); } - public Flowable transferEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public Flowable transferEventFlowable(DefaultBlockParameter startBlock, + DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); filter.addSingleTopic(EventEncoder.encode(TRANSFER_EVENT)); return transferEventFlowable(filter); } - public static ERC20 load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { - return new ERC20(contractAddress, web3j, credentials, contractGasProvider); - } - - public static ERC20 load(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { - return new ERC20(contractAddress, web3j, transactionManager, contractGasProvider); - } - - public Flowable approvalEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getApprovalEventFromLog(log)); - } - - public Flowable approvalEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(APPROVAL_EVENT)); - return approvalEventFlowable(filter); - } - public RemoteFunctionCall allowance(String owner, String spender) { final Function function = new Function(FUNC_ALLOWANCE, - Arrays.asList(new Address(160, owner), - new Address(160, spender)), - List.of(new TypeReference() { - })); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, owner), + new org.web3j.abi.datatypes.Address(160, spender)), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } public RemoteFunctionCall approve(String spender, BigInteger value) { final Function function = new Function( FUNC_APPROVE, - Arrays.asList(new Address(160, spender), - new Uint256(value)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, spender), + new org.web3j.abi.datatypes.generated.Uint256(value)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } public RemoteFunctionCall balanceOf(String account) { final Function function = new Function(FUNC_BALANCEOF, - List.of(new Address(160, account)), - List.of(new TypeReference() { - })); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, account)), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } public RemoteFunctionCall decimals() { final Function function = new Function(FUNC_DECIMALS, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } public RemoteFunctionCall name() { final Function function = new Function(FUNC_NAME, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, String.class); } public RemoteFunctionCall symbol() { final Function function = new Function(FUNC_SYMBOL, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, String.class); } public RemoteFunctionCall totalSupply() { final Function function = new Function(FUNC_TOTALSUPPLY, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } public RemoteFunctionCall transfer(String to, BigInteger value) { final Function function = new Function( FUNC_TRANSFER, - Arrays.asList(new Address(160, to), - new Uint256(value)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, to), + new org.web3j.abi.datatypes.generated.Uint256(value)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall transferFrom(String from, String to, BigInteger value) { + public RemoteFunctionCall transferFrom(String from, String to, + BigInteger value) { final Function function = new Function( FUNC_TRANSFERFROM, - Arrays.asList(new Address(160, from), - new Address(160, to), - new Uint256(value)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, from), + new org.web3j.abi.datatypes.Address(160, to), + new org.web3j.abi.datatypes.generated.Uint256(value)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } + @Deprecated + public static ERC20 load(String contractAddress, Web3j web3j, Credentials credentials, + BigInteger gasPrice, BigInteger gasLimit) { + return new ERC20(contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + @Deprecated + public static ERC20 load(String contractAddress, Web3j web3j, + TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + return new ERC20(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + public static ERC20 load(String contractAddress, Web3j web3j, Credentials credentials, + ContractGasProvider contractGasProvider) { + return new ERC20(contractAddress, web3j, credentials, contractGasProvider); + } + + public static ERC20 load(String contractAddress, Web3j web3j, + TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + return new ERC20(contractAddress, web3j, transactionManager, contractGasProvider); + } + public static class ApprovalEventResponse extends BaseEventResponse { public String owner; @@ -264,4 +269,4 @@ class ERC20 extends Contract { public BigInteger value; } -} +} \ No newline at end of file diff --git a/libs/visa/src/main/java/com/tangem/lib/visa/TangemBridgeProcessor.java b/libs/visa/src/main/java/com/tangem/lib/visa/TangemBridgeProcessor.java index b612de84bc..3352f39d85 100644 --- a/libs/visa/src/main/java/com/tangem/lib/visa/TangemBridgeProcessor.java +++ b/libs/visa/src/main/java/com/tangem/lib/visa/TangemBridgeProcessor.java @@ -1,11 +1,23 @@ package com.tangem.lib.visa; +import io.reactivex.Flowable; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import org.web3j.abi.EventEncoder; import org.web3j.abi.FunctionEncoder; import org.web3j.abi.TypeReference; -import org.web3j.abi.datatypes.*; +import org.web3j.abi.datatypes.Address; +import org.web3j.abi.datatypes.Bool; +import org.web3j.abi.datatypes.Event; +import org.web3j.abi.datatypes.Function; +import org.web3j.abi.datatypes.StaticStruct; +import org.web3j.abi.datatypes.Type; +import org.web3j.abi.datatypes.generated.Bytes16; import org.web3j.abi.datatypes.generated.Bytes32; -import org.web3j.abi.datatypes.generated.Bytes4; +import org.web3j.abi.datatypes.generated.Uint16; import org.web3j.abi.datatypes.generated.Uint256; import org.web3j.crypto.Credentials; import org.web3j.protocol.Web3j; @@ -20,40 +32,36 @@ import org.web3j.tx.Contract; import org.web3j.tx.TransactionManager; import org.web3j.tx.gas.ContractGasProvider; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import io.reactivex.Flowable; - /** *

Auto generated code. *

Do not modify! *

Please use the web3j command line tools, - * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the - * codegen module to update. + * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the + * codegen module to update. * - *

Generated with web3j version 1.5.2. + *

Generated with web3j version 1.6.1. */ @SuppressWarnings("rawtypes") -class TangemBridgeProcessor extends Contract { - public static final String BINARY = "608060405262278d006008553480156200001857600080fd5b506040516200272f3803806200272f8339810160408190526200003b9162000214565b60028054600580546001600160a01b03199081166001600160a01b038a8116919091179092556006805482168984161790556001600160a81b03199092166101008783160217909255600380549091169184169190911790556009819055620000a6600033620000b2565b5050505050506200027b565b600080620000c18484620000ef565b90508015620000e6576000848152600160205260409020620000e490846200019d565b505b90505b92915050565b6000828152602081815260408083206001600160a01b038516845290915281205460ff1662000194576000838152602081815260408083206001600160a01b03861684529091529020805460ff191660011790556200014b3390565b6001600160a01b0316826001600160a01b0316847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a4506001620000e9565b506000620000e9565b6000620000e6836001600160a01b03841660008181526001830160205260408120546200019457508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155620000e9565b80516001600160a01b03811681146200020f57600080fd5b919050565b600080600080600060a086880312156200022d57600080fd5b6200023886620001f7565b94506200024860208701620001f7565b93506200025860408701620001f7565b92506200026860608701620001f7565b9150608086015190509295509295909350565b6124a4806200028b6000396000f3fe608060405234801561001057600080fd5b506004361061028a5760003560e01c806383af133d1161015c578063c904ea39116100ce578063da90f3a011610087578063da90f3a0146105f1578063e24fd1f114610604578063e452f20d14610617578063e63ab1e91461062a578063e96ede8e14610651578063fe2208aa1461066457600080fd5b8063c904ea3914610589578063ca15c8731461059c578063cb37f3b2146105af578063d47ae89c146105c2578063d547741f146105cb578063d6d32b2a146105de57600080fd5b80639353ce4c116101205780639353ce4c146104f357806396bc563d1461051a578063978975591461052f578063a217fddf14610556578063b77c6d351461055e578063be4d18511461057657600080fd5b806383af133d146104a95780638456cb59146104bc5780639010d07c146104c457806391792d5b146104d757806391d14854146104e057600080fd5b80633f4ba83a11610200578063601c6065116101b9578063601c60651461042357806365ebf99a1461043657806370e4306e1461044957806373d3467c146104705780637f2fadb0146104835780638072a0221461049657600080fd5b80633f4ba83a146103ae5780634413e098146103b65780634b9bb0b4146103c95780634e6d8a73146103f057806356737951146104035780635c975abb1461041857600080fd5b80632f2ff15d116102525780632f2ff15d1461030e5780633013ce291461032157806336568abe1461034c57806337de81061461035f5780633d409a86146103725780633e1dbe1f1461038757600080fd5b806301ffc9a71461028f5780630f1071be146102b757806311dce771146102ce578063248a9ca3146102e35780632cc3264114610306575b600080fd5b6102a261029d366004611f47565b610677565b60405190151581526020015b60405180910390f35b6102c060085481565b6040519081526020016102ae565b6102e16102dc366004611f86565b6106a2565b005b6102c06102f1366004611fa3565b60009081526020819052604090206001015490565b6102e1610710565b6102e161031c366004611fbc565b61078a565b600354610334906001600160a01b031681565b6040516001600160a01b0390911681526020016102ae565b6102e161035a366004611fbc565b6107b5565b6102e161036d366004611fa3565b6107ed565b6102c060008051602061242683398151915281565b6102c07f80578ee961742bd59ed4d7f52c856d4014970acf79709a07f09c8c279d66cbb481565b6102e161084c565b6102e16103c4366004611fec565b610881565b6102c07fc62d23620f0c241b20cdec293f57ff9c0cc09ac7c55352efe3363184cd93032281565b6102e16103fe366004612018565b61094d565b6102c06000805160206123b183398151915281565b60025460ff166102a2565b6102e1610431366004612018565b610a57565b6102e1610444366004611f86565b610c7f565b6102c07f6ac2c8b5322a929d300af17766189693ff6db6939ffe690430ad4720baf47d1c81565b6102e161047e36600461205b565b610ce5565b6102e1610491366004611fec565b610e02565b6102e16104a4366004611fa3565b610ee5565b6102e16104b7366004611fec565b610f70565b6102e161102f565b6103346104d23660046120df565b611061565b6102c060075481565b6102a26104ee366004611fbc565b611080565b6102c07f79af5a85184b45263c1b3721b9c8eb1becd98e55ade5a61da0fed18b90b8325681565b6102c060008051602061230183398151915281565b6102c07f581be84b4822abe392a3bcac0d4d656e4cd57faf2e321a32108215cdd4a8365681565b6102c0600081565b6002546103349061010090046001600160a01b031681565b600654610334906001600160a01b031681565b6102e1610597366004612101565b6110a9565b6102c06105aa366004611fa3565b6111aa565b600554610334906001600160a01b031681565b6102c060095481565b6102e16105d9366004611fbc565b6111c1565b6102e16105ec366004611fa3565b6111e6565b6102e16105ff366004611fec565b611270565b6102e161061236600461214b565b6114ed565b610334610625366004611f86565b61162e565b6102c07f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a81565b6102e161065f366004612018565b61168d565b6102e1610672366004612018565b611782565b60006001600160e01b03198216635a05180f60e01b148061069c575061069c8261185c565b92915050565b6000805160206123018339815191526106ba81611891565b600680546001600160a01b0319166001600160a01b0384169081179091556040519081527f68c7b435e0ad7bfb2140fab0735300efbda206f62906049e0b35763cef67cc5e906020015b60405180910390a15050565b600a54339061071e8261189b565b6000811160405180606001604052806029815260200161244660299139906107625760405162461bcd60e51b8152600401610759919061219d565b60405180910390fd5b506000600a55600654600354610786916001600160a01b0391821691168484611946565b5050565b6000828152602081905260409020600101546107a581611891565b6107af83836119a0565b50505050565b6001600160a01b03811633146107de5760405163334bd91960e11b815260040160405180910390fd5b6107e882826119d5565b505050565b7f80578ee961742bd59ed4d7f52c856d4014970acf79709a07f09c8c279d66cbb461081781611891565b60078290556040518281527fb5aa183eb20407e22587bcd13d5c82a85835a4bd60e2a13d7c7efee3c2e9ed4890602001610704565b7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a61087681611891565b61087e611a02565b50565b7f581be84b4822abe392a3bcac0d4d656e4cd57faf2e321a32108215cdd4a836566108ab81611891565b60405163b9603bdf60e01b8152600481018390526001600160a01b0384169063b9603bdf90602401600060405180830381600087803b1580156108ed57600080fd5b505af1158015610901573d6000803e3d6000fd5b50505050826001600160a01b03167fc8818db5b3e4e986e2f21e002090b1513e17c05a7a326c3737af624bf6ef78c48360405161094091815260200190565b60405180910390a2505050565b610955611a54565b7f6ac2c8b5322a929d300af17766189693ff6db6939ffe690430ad4720baf47d1c61097f81611891565b600061098a8561162e565b905060006007548461099c91906121e6565b604051630381b4dd60e01b815260048101879052602481018290529091506001600160a01b03831690630381b4dd90604401600060405180830381600087803b1580156109e857600080fd5b505af11580156109fc573d6000803e3d6000fd5b5050505084866001600160a01b03167fa77a4ae2cfa0139350d70cd52434d01ae72e9ec897ae89be03580f1ff00dd3d086600754604051610a47929190918252602082015260400190565b60405180910390a3505050505050565b610a5f611a54565b6000805160206123b1833981519152610a7781611891565b60408051808201909152601b81527f353130307c436f6d6d6f6e3a20616d6f756e74206973207a65726f0000000000602082015282610ac95760405162461bcd60e51b8152600401610759919061219d565b506000610ad58561162e565b6003546006546040516370a0823160e01b81526001600160a01b0391821660048201529293506000929116906370a0823190602401602060405180830381865afa158015610b27573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b4b91906121f9565b9050838110156040518060400160405280601f81526020017f353130317c436f6d6d6f6e3a20696e73756666696369656e742066756e64730081525090610ba55760405162461bcd60e51b8152600401610759919061219d565b50600a84905560405163f89db45760e01b8152600481018690526001600160a01b0383169063f89db45790602401600060405180830381600087803b158015610bed57600080fd5b505af1158015610c01573d6000803e3d6000fd5b50505050600a546000146040518060600160405280602e81526020016123d1602e913990610c425760405162461bcd60e51b8152600401610759919061219d565b5084866001600160a01b03167fa5411d116afcb8415f0019792cccf595f94852ba82886dacbf756d9c3ce85f3f86604051610a4791815260200190565b600080516020612301833981519152610c9781611891565b600580546001600160a01b0319166001600160a01b0384169081179091556040519081527fccbdeb71dc680b2f0fd85f93e7ef0f70eda32a12b918c9715be07bb646360e5990602001610704565b610ced611a54565b600080516020612426833981519152610d0581611891565b6000610d108861162e565b9050600060075487610d2291906121e6565b6040516334284dfb60e01b8152600481018a9052602481018290526001600160801b03198816604482015261ffff8716606482015285151560848201529091506001600160a01b038316906334284dfb9060a401600060405180830381600087803b158015610d9057600080fd5b505af1158015610da4573d6000803e3d6000fd5b5050505087896001600160a01b03167f92c1dbadbb7b791052341ce7c185ca37861e432438355dc3e1642b7c1287609a89600754604051610def929190918252602082015260400190565b60405180910390a3505050505050505050565b610e0a611a54565b7f79af5a85184b45263c1b3721b9c8eb1becd98e55ade5a61da0fed18b90b83256610e3481611891565b6000610e3f8461162e565b604051636df6c7d560e11b8152600481018590529091506001600160a01b0382169063dbed8faa90602401600060405180830381600087803b158015610e8457600080fd5b505af1158015610e98573d6000803e3d6000fd5b50505050836001600160a01b03167f466723bb873015d5baf515fce6b0c8df2cb154adfd8badb6bc77a620a6ef676184604051610ed791815260200190565b60405180910390a250505050565b600080516020612301833981519152610efd81611891565b6283d60082106040518060600160405280602a81526020016122d7602a913990610f3a5760405162461bcd60e51b8152600401610759919061219d565b5060088290556040518281527fdc10143650bb79cd7a92cdf792545dcc2c3b0a719bebb3d83a8a21bb8fbfc3d690602001610704565b7f581be84b4822abe392a3bcac0d4d656e4cd57faf2e321a32108215cdd4a83656610f9a81611891565b6040516306b2a38360e41b8152600481018390526001600160a01b03841690636b2a383090602401600060405180830381600087803b158015610fdc57600080fd5b505af1158015610ff0573d6000803e3d6000fd5b50505050826001600160a01b03167fcc1685e553848099cad0e11271e780371c44b5ed92a54552fbedf9f1b6b9f2478360405161094091815260200190565b7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a61105981611891565b61087e611a7a565b60008281526001602052604081206110799083611ab7565b9392505050565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b6110b1611a54565b6000805160206124268339815191526110c981611891565b60006110d48661162e565b90506000600754856110e691906121e6565b6040516320199e7960e21b8152600481018890526024810182905285151560448201529091506001600160a01b0383169063806679e490606401600060405180830381600087803b15801561113a57600080fd5b505af115801561114e573d6000803e3d6000fd5b5050505085876001600160a01b03167f92c1dbadbb7b791052341ce7c185ca37861e432438355dc3e1642b7c1287609a87600754604051611199929190918252602082015260400190565b60405180910390a350505050505050565b600081815260016020526040812061069c90611ac3565b6000828152602081905260409020600101546111dc81611891565b6107af83836119d5565b6000805160206123018339815191526111fe81611891565b610e1082106040518060600160405280602781526020016123ff602791399061123a5760405162461bcd60e51b8152600401610759919061219d565b5060098290556040518281527f7f63f876249f25f7856e620802edd09af38a83c9c6040fcdb37179154030831190602001610704565b611278611a54565b6000805160206123b183398151915261129081611891565b600061129b8461162e565b604051631902cad960e31b8152600481018590529091506000906001600160a01b0383169063c81656c890602401602060405180830381865afa1580156112e6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061130a91906121f9565b6003546006546040516370a0823160e01b81526001600160a01b0391821660048201529293506000929116906370a0823190602401602060405180830381865afa15801561135c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061138091906121f9565b90506000821160405180606001604052806027815260200161238a60279139906113bd5760405162461bcd60e51b8152600401610759919061219d565b5060408051808201909152601f81527f353130317c436f6d6d6f6e3a20696e73756666696369656e742066756e6473006020820152828210156114135760405162461bcd60e51b8152600401610759919061219d565b50600a82905560405163e3dbffd560e01b8152600481018690526001600160a01b0384169063e3dbffd590602401600060405180830381600087803b15801561145b57600080fd5b505af115801561146f573d6000803e3d6000fd5b50505050600a546000146040518060600160405280602e81526020016123d1602e9139906114b05760405162461bcd60e51b8152600401610759919061219d565b5084866001600160a01b03167fdfab01fc691e8b69fca482c65142d1bcfeafeb9eb6e9fd5220867d7196d9476e84604051610a4791815260200190565b7fc62d23620f0c241b20cdec293f57ff9c0cc09ac7c55352efe3363184cd93032261151781611891565b6115208261189b565b306001600160a01b0316826001600160a01b031663ce1b1d436040518163ffffffff1660e01b8152600401602060405180830381865afa158015611568573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061158c9190612212565b6001600160a01b03161460405180606001604052806032815260200161235860329139906115cd5760405162461bcd60e51b8152600401610759919061219d565b506001600160a01b0383811660008181526004602090815260409182902080546001600160a01b0319169487169485179055905192835290917f0c1464e8c817204497a2076f3b466fe64093e83033c83303b0cb44001cd9b6b59101610940565b6001600160a01b03808216600090815260046020908152604080832054815160608101909252602f80835293941692831515926122a890830139906116865760405162461bcd60e51b8152600401610759919061219d565b5092915050565b611695611a54565b6000805160206124268339815191526116ad81611891565b60006116b88561162e565b9050600083156116ca576007546116cd565b60005b905060006116db82866121e6565b60405163e4d7ddb960e01b815260048101889052602481018290529091506001600160a01b0384169063e4d7ddb990604401600060405180830381600087803b15801561172757600080fd5b505af115801561173b573d6000803e3d6000fd5b505060408051888152602081018690528993506001600160a01b038b1692507f8ba3ad1bd4ff5c3d75861d56df66b7942244d700f4d4cb67df693bac78e805a29101611199565b61178a611a54565b6000805160206123b18339815191526117a281611891565b60006117ad8561162e565b60405163456575e560e01b815260048101869052602481018590529091506001600160a01b0382169063456575e590604401600060405180830381600087803b1580156117f957600080fd5b505af115801561180d573d6000803e3d6000fd5b5050505083856001600160a01b03167f6f60b80b8dcecc3741c03485554a9a35ced96ebba66405fd324caf12a16261da8560405161184d91815260200190565b60405180910390a35050505050565b60006001600160e01b03198216637965db0b60e01b148061069c57506301ffc9a760e01b6001600160e01b031983161461069c565b61087e8133611acd565b6002546040516385bb392360e01b81526001600160a01b038381166004830152610100909204909116906385bb392390602401602060405180830381865afa1580156118eb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061190f919061222f565b60405180606001604052806037815260200161232160379139906107865760405162461bcd60e51b8152600401610759919061219d565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b1790526107af908590611b06565b6000806119ad8484611b69565b905080156110795760008481526001602052604090206119cd9084611bfb565b509392505050565b6000806119e28484611c10565b905080156110795760008481526001602052604090206119cd9084611c7b565b611a0a611c90565b6002805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b60025460ff1615611a785760405163d93c066560e01b815260040160405180910390fd5b565b611a82611a54565b6002805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258611a373390565b60006110798383611cb3565b600061069c825490565b611ad78282611080565b6107865760405163e2517d3f60e01b81526001600160a01b038216600482015260248101839052604401610759565b6000611b1b6001600160a01b03841683611cdd565b90508051600014158015611b40575080806020019051810190611b3e919061222f565b155b156107e857604051635274afe760e01b81526001600160a01b0384166004820152602401610759565b6000611b758383611080565b611bf3576000838152602081815260408083206001600160a01b03861684529091529020805460ff19166001179055611bab3390565b6001600160a01b0316826001600160a01b0316847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a450600161069c565b50600061069c565b6000611079836001600160a01b038416611ceb565b6000611c1c8383611080565b15611bf3576000838152602081815260408083206001600160a01b0386168085529252808320805460ff1916905551339286917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a450600161069c565b6000611079836001600160a01b038416611d32565b60025460ff16611a7857604051638dfc202b60e01b815260040160405180910390fd5b6000826000018281548110611cca57611cca61224c565b9060005260206000200154905092915050565b606061107983836000611e25565b6000818152600183016020526040812054611bf35750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561069c565b60008181526001830160205260408120548015611e1b576000611d56600183612262565b8554909150600090611d6a90600190612262565b9050808214611dcf576000866000018281548110611d8a57611d8a61224c565b9060005260206000200154905080876000018481548110611dad57611dad61224c565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080611de057611de0612275565b60019003818190600052602060002001600090559055856001016000868152602001908152602001600020600090556001935050505061069c565b600091505061069c565b606081471015611e4a5760405163cd78605960e01b8152306004820152602401610759565b600080856001600160a01b03168486604051611e66919061228b565b60006040518083038185875af1925050503d8060008114611ea3576040519150601f19603f3d011682016040523d82523d6000602084013e611ea8565b606091505b5091509150611eb8868383611ec2565b9695505050505050565b606082611ed757611ed282611f1e565b611079565b8151158015611eee57506001600160a01b0384163b155b15611f1757604051639996b31560e01b81526001600160a01b0385166004820152602401610759565b5080611079565b805115611f2e5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b600060208284031215611f5957600080fd5b81356001600160e01b03198116811461107957600080fd5b6001600160a01b038116811461087e57600080fd5b600060208284031215611f9857600080fd5b813561107981611f71565b600060208284031215611fb557600080fd5b5035919050565b60008060408385031215611fcf57600080fd5b823591506020830135611fe181611f71565b809150509250929050565b60008060408385031215611fff57600080fd5b823561200a81611f71565b946020939093013593505050565b60008060006060848603121561202d57600080fd5b833561203881611f71565b95602085013595506040909401359392505050565b801515811461087e57600080fd5b60008060008060008060c0878903121561207457600080fd5b863561207f81611f71565b9550602087013594506040870135935060608701356001600160801b0319811681146120aa57600080fd5b9250608087013561ffff811681146120c157600080fd5b915060a08701356120d18161204d565b809150509295509295509295565b600080604083850312156120f257600080fd5b50508035926020909101359150565b6000806000806080858703121561211757600080fd5b843561212281611f71565b9350602085013592506040850135915060608501356121408161204d565b939692955090935050565b6000806040838503121561215e57600080fd5b823561216981611f71565b91506020830135611fe181611f71565b60005b8381101561219457818101518382015260200161217c565b50506000910152565b60208152600082518060208401526121bc816040850160208701612179565b601f01601f19169190910160400192915050565b634e487b7160e01b600052601160045260246000fd5b8082018082111561069c5761069c6121d0565b60006020828403121561220b57600080fd5b5051919050565b60006020828403121561222457600080fd5b815161107981611f71565b60006020828403121561224157600080fd5b81516110798161204d565b634e487b7160e01b600052603260045260246000fd5b8181038181111561069c5761069c6121d0565b634e487b7160e01b600052603160045260246000fd5b6000825161229d818460208701612179565b919091019291505056fe353230327c50726f636573736f723a206e6f207061796d656e74206163636f756e7420666f72207468652063617264353231307c50726f636573736f723a20736574746c656d656e7420706572696f6420746f6f206c6f6e67fa658e47c75a6eb0e27156b2f72e1b295c98b53e8a1d238d7ca6ea6345b34880353230307c50726f636573736f723a207061796d656e74206163636f756e74206e6f74206465706c6f79656420627920666163746f7279353230317c50726f636573736f723a207061796d656e74206163636f756e742070726f636573736f72206d69736d61746368353235307c50726f636573736f723a20726566756e64207265636f7264206e6f7420666f756e64d77cc12a543481a2b3ef8fd055979569715a10db9879cd9e664395eca3a54dff353235327c50726f636573736f723a20726566756e6420746f2070726f63657373207761736e2774207265736574353231317c50726f636573736f723a2073656375726974792064656c617920746f6f206c6f6e67a567244cad934c87b7f7e15b15dd0e75f95a76afd1916f8eab4a391410d6f278353235317c50726f636573736f723a20726566756e6420746f2070726f63657373206973207a65726fa2646970667358221220098c0e97a9ad9d3bb5cd4459c44925a6fb83295823a48d337beb3c254c7b728b64736f6c63430008160033"; +public class TangemBridgeProcessor extends Contract { + public static final String BINARY = "608060405234620000345762000022620000186200012e565b939290926200022c565b604051612dc86200057b8239612dc890f35b600080fd5b634e487b7160e01b600052604160045260246000fd5b90601f01601f191681019081106001600160401b038211176200007157604052565b62000039565b906200008e6200008660405190565b92836200004f565b565b6001600160a01b031690565b90565b6001600160a01b0381165b036200003457565b905051906200008e826200009f565b80620000aa565b905051906200008e82620000c1565b919060a0838203126200003457620000f08184620000b2565b92620001008260208301620000b2565b926200009c620001148460408501620000b2565b936080620001268260608701620000b2565b9401620000c8565b620001516200334380380380620001458162000077565b928339810190620000d7565b9091929394565b906001600160a01b03905b9181191691161790565b6200009c9062000090906001600160a01b031682565b6200009c906200016d565b6200009c9062000183565b90620001ad6200009c620001b5926200018e565b825462000158565b9055565b90610100600160a81b039060081b62000163565b90620001e16200009c620001b5926200018e565b8254620001b9565b906000199062000163565b6200009c6200009c6200009c9290565b90620002186200009c620001b592620001f4565b8254620001e9565b6200009c6000620001f4565b92620002676200026d6200027d946200026762000275956200025f620002859a9962000257620002c3565b600462000199565b600562000199565b6200018e565b6002620001cd565b600362000199565b600662000204565b6200029b6200029362000220565b3390620002e1565b50565b9060ff9062000163565b90620002bb6200009c620001b592151590565b82546200029e565b6200008e60006002620002a8565b905b600052602052604060002090565b620002ed828262000324565b9182620002f957505090565b6200030e6200009c62000314936001620002d1565b620003d2565b5090565b90620002d3906200018e565b6200033862000334838362000419565b1590565b15620003b8576200036460016200035e846000620003578682620002d1565b0162000318565b620002a8565b620003836200037c62000375339390565b936200018e565b916200018e565b917f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d620003af60405190565b600090a4600190565b5050600090565b6200009c9081906001600160a01b031681565b906200040c6200040862000402620003fc60006200009c96620003f3600090565b50019462000183565b620003bf565b620001f4565b9190565b620004e4565b5460ff1690565b6200009c916000620003576200043a9362000432600090565b5082620002d1565b62000412565b634e487b7160e01b600052603260045260246000fd5b80548210156200047b5762000472600191600052602060002090565b91020190600090565b62000440565b9160001960089290920291821b911b62000163565b9190620004a86200009c620001b59390565b90835462000481565b9081549168010000000000000000831015620000715782620004dd9160016200008e9501815562000456565b9062000496565b620004f462000334838362000542565b15620003b8576200052991620005239060016200051b84620005178482620004b1565b5490565b9301620002d1565b62000204565b600190565b6200009c9081565b6200009c90546200052e565b620005659160016200055f9262000557600090565b5001620002d1565b62000536565b62000575620004086000620001f4565b14159056fe6080604052600436101561001257600080fd5b60003560e01c806301ffc9a71461030257806311dce771146102fd578063248a9ca3146102f857806328e14f24146102f35780632cc32641146102ee5780632e44b939146102e95780632f2ff15d146102e45780633013ce29146102df57806336568abe146102da5780633c1a5012146102d55780633d409a86146102d05780633f4ba83a146102cb57806341d2ac20146102c65780634413e098146102c157806356737951146102bc5780635c975abb146102b757806365ebf99a146102b257806368b40573146102ad5780636a7554c0146102a857806370e4306e146102a35780637123925e1461029e57806383af133d146102995780638456cb59146102945780639010d07c1461028f57806391d148541461028a5780639353ce4c1461028557806396bc563d14610280578063978975591461027b57806399fef6bb146102765780639e4da86214610271578063a217fddf1461026c578063a9eb7b5a14610267578063aca821f714610262578063b77c6d351461025d578063be4d185114610258578063c0e678f814610253578063c8de6d171461024e578063ca15c87314610249578063cb37f3b214610244578063d47ae89c1461023f578063d547741f1461023a578063d595130814610235578063d6d32b2a14610230578063e0a86ef71461022b578063e63ab1e914610226578063eb465bca146102215763f0cb94980361031a57610d95565b610d76565b610cce565b610caf565b610c04565b610be8565b610bcf565b610bb4565b610b77565b610b50565b610b37565b610b1b565b610a9f565b610a78565b610a50565b610a37565b610a1c565b6109c4565b6109a9565b610970565b610949565b610910565b6108f4565b6108be565b61088a565b610871565b610838565b6107ff565b6107e6565b6107ca565b61077d565b61075e565b610725565b61070c565b6106d7565b61068f565b610668565b610650565b610637565b610610565b61058d565b610548565b61046a565b610431565b6103fa565b6103b6565b610349565b6001600160e01b031981165b0361031a57565b600080fd5b9050359061032c82610307565b565b9060208282031261031a576103429161031f565b90565b9052565b3461031a5761037661036461035f36600461032e565b610dce565b60405191829182901515815260200190565b0390f35b6001600160a01b031690565b6001600160a01b038116610313565b9050359061032c82610386565b9060208282031261031a5761034291610395565b3461031a576103ce6103c93660046103a2565b610e81565b604051005b80610313565b9050359061032c826103d3565b9060208282031261031a57610342916103d9565b3461031a576103766104156104103660046103e6565b610eab565b6040515b9182918290815260200190565b600091031261031a57565b3461031a57610441366004610426565b6103767f350e76a198c654a14c1f5ccac917c5f939f08970b53375e57483f9623a24399c610415565b3461031a5761047a366004610426565b6103ce611072565b908160e091031261031a5790565b909182601f8301121561031a5781359167ffffffffffffffff831161031a57602001926001830284011161031a57565b6101808183031261031a576104d58282610395565b926104e38360208401610395565b926104f18160408501610482565b926105008261012083016103d9565b9261014082013567ffffffffffffffff811161031a5783610522918401610490565b92909361016082013567ffffffffffffffff811161031a576105449201610490565b9091565b3461031a576103ce61055b3660046104c0565b969590959491949392936113fd565b919060408382031261031a5761034290602061058682866103d9565b9401610395565b3461031a576103ce6105a036600461056a565b9061142b565b610342916008021c6001600160a01b031690565b9061034291546105a6565b610342600060036105ba565b6103429061037a906001600160a01b031682565b610342906105d1565b610342906105e5565b610345906105ee565b60208101929161032c91906105f7565b3461031a57610620366004610426565b61037661062b6105c5565b60405191829182610600565b3461031a576103ce61064a36600461056a565b90611435565b3461031a576103ce6106633660046103a2565b611530565b3461031a57610678366004610426565b610376600080516020612d73833981519152610415565b3461031a5761069f366004610426565b6103ce61156d565b909160608284031261031a576103426106c08484610395565b9360406106d08260208701610395565b94016103d9565b3461031a576103ce6106ea3660046106a7565b916116eb565b919060408382031261031a576103429060206106d08286610395565b3461031a576103ce61071f3660046106f0565b906117a3565b3461031a57610735366004610426565b6103767fd77cc12a543481a2b3ef8fd055979569715a10db9879cd9e664395eca3a54dff610415565b3461031a5761076e366004610426565b61037661036460025460ff1690565b3461031a576103ce6107903660046103a2565b6117f8565b60808183031261031a576107a98282610395565b926103426107ba8460208501610395565b9360606106d082604087016103d9565b3461031a576103ce6107dd366004610795565b92919091611a60565b3461031a576103ce6107f93660046106f0565b90611b0f565b3461031a5761080f366004610426565b6103767f6ac2c8b5322a929d300af17766189693ff6db6939ffe690430ad4720baf47d1c610415565b3461031a57610848366004610426565b6103767fca900a2e3cfbbbb78b9095304e53ddb6de902bbef4638bd3c79fe9cf3a441202610415565b3461031a576103ce6108843660046106f0565b90611bbc565b3461031a5761089a366004610426565b6103ce611bfa565b919060408382031261031a576103429060206106d082866103d9565b3461031a576103766108da6108d43660046108a2565b90611c02565b604051918291826001600160a01b03909116815260200190565b3461031a5761037661036461090a36600461056a565b90611c2c565b3461031a57610920366004610426565b6103767f79af5a85184b45263c1b3721b9c8eb1becd98e55ade5a61da0fed18b90b83256610415565b3461031a57610959366004610426565b610376600080516020612d53833981519152610415565b3461031a57610980366004610426565b6103767f581be84b4822abe392a3bcac0d4d656e4cd57faf2e321a32108215cdd4a83656610415565b3461031a576103766103646109bf3660046103a2565b611c55565b3461031a576109d4366004610426565b6103767f863747851594ece3a3681369cf3710069c605efc30fccffb517283afa5efce91610415565b6103426103426103429290565b61034260006109fd565b610342610a0a565b3461031a57610a2c366004610426565b610376610415610a14565b3461031a576103ce610a4a3660046106a7565b91611d3b565b3461031a576103ce610a63366004610795565b92919091611e22565b610342600160026105ba565b3461031a57610a88366004610426565b61037661062b610a6c565b610342600060056105ba565b3461031a57610aaf366004610426565b6103766108da610a93565b801515610313565b9050359061032c82610aba565b919060a08382031261031a57610ae58184610395565b92610af38260208301610395565b92610342610b0484604085016103d9565b936080610b1482606087016103d9565b9401610ac2565b3461031a576103ce610b2e366004610acf565b93929092611f2f565b3461031a576103ce610b4a3660046106a7565b91611ff3565b3461031a57610376610415610b663660046103e6565b611ffe565b610342600060046105ba565b3461031a57610b87366004610426565b6103766108da610b6b565b610342916008021c81565b906103429154610b92565b61034260006006610b9d565b3461031a57610bc4366004610426565b610376610415610ba8565b3461031a576103ce610be236600461056a565b90612030565b3461031a576103ce610bfb366004610795565b929190916120fc565b3461031a576103ce610c173660046103e6565b6121ce565b6001600160801b03198116610313565b9050359061032c82610c1c565b61ffff8116610313565b9050359061032c82610c39565b60e08183031261031a57610c648282610395565b92610c728360208401610395565b92610c8081604085016103d9565b92610c8e82606083016103d9565b92610342610c9f8460808501610c2c565b9360c0610b148260a08701610c43565b3461031a576103ce610cc2366004610c50565b959490949391936122b4565b3461031a57610cde366004610426565b6103767f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a610415565b9060c08282031261031a57610d1c8183610395565b92610d2a8260208501610395565b92610d3883604083016103d9565b92610d4681606084016103d9565b92608083013567ffffffffffffffff811161031a5782610d6d60a094610342938701610490565b94909501610ac2565b3461031a576103ce610d89366004610d07565b95949094939193612397565b3461031a57610da5366004610426565b6103767f7245419aa9f77e6e9f023dc59852153b8258661dd13b3e1a61fe9e8a5977aa74610415565b635a05180f60e01b6001600160e01b0319821614908115610ded575090565b61034291506123a6565b61032c90610e17600080516020612d538339815191526123e0565b6123e0565b610e4c565b906001600160a01b03905b9181191691161790565b90610e41610342610e48926105ee565b8254610e1c565b9055565b610e7c7f68c7b435e0ad7bfb2140fab0735300efbda206f62906049e0b35763cef67cc5e916108da816005610e31565b0390a1565b61032c90610df7565b905b600052602052604060002090565b6103429081565b6103429054610e9a565b6001610ec461034292610ebc600090565b506000610e8a565b01610ea1565b634e487b7160e01b600052604160045260246000fd5b90601f01601f1916810190811067ffffffffffffffff821117610f0257604052565b610eca565b9061032c610f1460405190565b9283610ee0565b67ffffffffffffffff8111610f0257602090601f01601f19160190565b0190565b90610f4e610f4983610f1b565b610f07565b918252565b610f5d6029610f3c565b7f353235317c50726f636573736f723a20726566756e6420746f2070726f63657360208201526873206973207a65726f60b81b604082015290565b610342610f53565b610342610f98565b60005b838110610fbb5750506000910152565b8181015183820152602001610fab565b610fec610ff5602093610f3893610fe0815190565b80835293849260200190565b95869101610fa8565b601f01601f191690565b602080825261034292910190610fcb565b156110185750565b61103a9061102560405190565b62461bcd60e51b815291829160048301610fff565b0390fd5b9060001990610e27565b90611058610342610e48926109fd565b825461103e565b6103429061037a565b610342905461105f565b61032c336110806007610ea1565b9061108a81612475565b6110a761109760006109fd565b83116110a1610fa0565b90611010565b6110bb6110b460006109fd565b6007611048565b6110c56003611068565b6110cf6005611068565b90612537565b9061032c979695949392916110e8612581565b61032c979695949392919061111c7f863747851594ece3a3681369cf3710069c605efc30fccffb517283afa5efce916123e0565b61130e565b6103429060081c61037a565b6103429054611121565b9050519061032c82610386565b9060208282031261031a5761034291611137565b50610342906020810190610ac2565b50610342906020810190610c2c565b50610342906020810190610c43565b9060206111b661032c936111ad61119f6000830183611167565b6001600160801b0319168552565b82810190611176565b61ffff16910152565b506103429060208101906103d9565b90606061122361032c936111ec6111e860008301836111bf565b8552565b6112036111fc60208301836111bf565b6020860152565b61121a61121360408301836111bf565b6040860152565b828101906111bf565b910152565b90606061125e61032c936112486112426000830183611158565b15158552565b611259602082016020860190611185565b820190565b9101906111ce565b90826000939282370152565b9190610ff58161128981610f389560209181520190565b8095611266565b9694906112f3946112dd89956112d36103429c9a966112c36112e59660006101808c019b01906001600160a01b03169052565b6001600160a01b031660208d0152565b60408b0190611228565b610120890152565b868303610140880152611272565b92610160818503910152611272565b6040513d6000823e3d90fd5b9485611354839960209795969960009461133061132b600261112d565b6105ee565b9761133a60405190565b9c8d9a8b998a98632e44b93960e01b8a5260048a01611290565b03925af19081156113f8577f49f84c15b9f052fd24e68e7760c0bc11d05c5a058721d4dfc2c5c02866f84cd6926000926113c3575b50611393906105ee565b926113be6113a060405190565b928392836001600160a01b0391821681529116602082015260400190565b0390a2565b6113939192506113ea9060203d6020116113f1575b6113e28183610ee0565b810190611144565b9190611389565b503d6113d8565b611302565b9061032c979695949392916110d5565b9061032c9161141e610e1282610eab565b906114289161259f565b50565b9061032c9161140d565b9061143f3361037a565b6001600160a01b0382160361145757611428916125ce565b60405163334bd91960e11b8152600490fd5b61032c906114967fca900a2e3cfbbbb78b9095304e53ddb6de902bbef4638bd3c79fe9cf3a4412026123e0565b6114a261132b826105ee565b90813b1561031a5760006114b560405190565b6337a1d41560e01b8152928390600490829084905af19182156113f8576114e192611512575b506105ee565b7feb17a79f3df37ac5b011bba24abb3d48db9d0dd39110551346f1c30bfa0fe70161150b60405190565b80806113be565b61152a9060006115228183610ee0565b810190610426565b386114db565b61032c90611469565b6115627f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a6123e0565b61032c61032c61265f565b61032c611539565b9061032c9291611583612581565b61032c9291906115b27f79af5a85184b45263c1b3721b9c8eb1becd98e55ade5a61da0fed18b90b832566123e0565b6115f9565b9050519061032c826103d3565b9060208282031261031a57610342916115b7565b6001600160a01b03909116815260408101929161032c9160200152565b0152565b602061163360009461160d61132b856105ee565b9061161760405190565b968793849283919063f93184c160e01b835289600484016115d8565b03925af19283156113f85760009361168c575b5061168761167d6116777f8c7f40acbbdddd09014432da06798dda241de8adb409a62f3611f9a0d63d6b5b936105ee565b936105ee565b9361041960405190565b0390a3565b7f8c7f40acbbdddd09014432da06798dda241de8adb409a62f3611f9a0d63d6b5b91935061167d6116776116da6116879360203d6020116116e4575b6116d28183610ee0565b8101906115c4565b9593505050611646565b503d6116c8565b9061032c9291611575565b61032c91906117247f581be84b4822abe392a3bcac0d4d656e4cd57faf2e321a32108215cdd4a836566123e0565b61173061132b826105ee565b803b1561031a57600061174260405190565b63b9603bdf60e01b815260048101859052918290602490829084905af180156113f8577fc8818db5b3e4e986e2f21e002090b1513e17c05a7a326c3737af624bf6ef78c4926113be926117999261151257506105ee565b9261041960405190565b9061032c916116f6565b61032c906117c8600080516020612d538339815191526123e0565b610e7c7fccbdeb71dc680b2f0fd85f93e7ef0f70eda32a12b918c9715be07bb646360e59916108da816004610e31565b61032c906117ad565b9061032c939291611810612581565b61032c939291906118407fd77cc12a543481a2b3ef8fd055979569715a10db9879cd9e664395eca3a54dff6123e0565b6118e2565b61184f601f610f3c565b7f353130317c436f6d6d6f6e3a20696e73756666696369656e742066756e647300602082015290565b610342611845565b610342611878565b611892602e610f3c565b7f353235327c50726f636573736f723a20726566756e6420746f2070726f63657360208201526d1cc81dd85cdb89dd081c995cd95d60921b604082015290565b610342611888565b6103426118d2565b6118eb846126aa565b61193860206118fd61132b6003611068565b6119076005611068565b9061191160405190565b938492839182916370a0823160e01b5b83526001600160a01b031660048301526024820190565b03915afa80156113f85761196491600091611a41575b50859061195a565b9190565b10156110a1611880565b61196f846007611048565b61197b61132b826105ee565b90813b1561031a57600061198e60405190565b92839063bc77d49560e01b82528183816119ac8a8a600484016115d8565b03925af19081156113f857611a16611a10611a1c927f6de2d5fa0b9cac9e48f300a350314e447648d158a5067a56513a46396f1b638a95611a2695611a2b575b5061132b6119fa6007610ea1565b611a0761195660006109fd565b146110a16118da565b946105ee565b946109fd565b9461041960405190565b0390a4565b611a3b9060006115228183610ee0565b386119ec565b611a5a915060203d6020116116e4576116d28183610ee0565b3861194e565b9061032c939291611801565b61032c9190611a9a7f7245419aa9f77e6e9f023dc59852153b8258661dd13b3e1a61fe9e8a5977aa746123e0565b611aa661132b826105ee565b803b1561031a576000611ab860405190565b6377627c0960e11b815260048101859052918290602490829084905af180156113f8577faeb30c8dda75512b9946228f4034031bd3f99d91f2c4ed3806be677f3429cd80926113be926117999261151257506105ee565b9061032c91611a6c565b61032c9190611b477f581be84b4822abe392a3bcac0d4d656e4cd57faf2e321a32108215cdd4a836566123e0565b611b5361132b826105ee565b803b1561031a576000611b6560405190565b6306b2a38360e41b815260048101859052918290602490829084905af180156113f8577fcc1685e553848099cad0e11271e780371c44b5ed92a54552fbedf9f1b6b9f247926113be926117999261151257506105ee565b9061032c91611b19565b611bef7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a6123e0565b61032c61032c612705565b61032c611bc6565b90611c1d61034261034293611c15600090565b506001610e8a565b612723565b90610e8c906105ee565b610342916000611c48611c4e93611c41600090565b5082610e8a565b01611c22565b5460ff1690565b610342907f350e76a198c654a14c1f5ccac917c5f939f08970b53375e57483f9623a24399c611c2c565b9061032c9291611c8d612581565b61032c929190611cbc7f6ac2c8b5322a929d300af17766189693ff6db6939ffe690430ad4720baf47d1c6123e0565b611cc861132b826105ee565b90813b1561031a576000611cdb60405190565b928390633e0fc2ff60e01b8252818381611cf98a8a600484016115d8565b03925af19182156113f8576116776116879261167d927f7ea7d3b21868f19f237e4e91d95cc953f5a73d5b2c1862712635f3ef384887fd9561151257506105ee565b9061032c9291611c7f565b9061032c939291611d55612581565b61032c93929190611d73600080516020612d738339815191526123e0565b611d9f565b6001600160a01b03909116815260608101939261032c9290916040916115f5906020830152565b611dab61132b826105ee565b90813b1561031a576000611dbe60405190565b9283906374b76f4760e11b8252818381611ddd8b8b8b60048501611d78565b03925af19081156113f857611a16611a10611a1c927f0ee23c67813e7ee061931ccc27dd2be79deb9f90738c9649947a27243cd6d81f95611a269561151257506105ee565b9061032c939291611d46565b9061032c94939291611e3e612581565b61032c9493929190611e5d600080516020612d738339815191526123e0565b611ea2565b611e9a61032c94611e93606094989795611e8c608086019a60008701906001600160a01b03169052565b6020850152565b6040830152565b019015159052565b9390919293611eb361132b826105ee565b803b1561031a57611eea600093918492611ecc60405190565b95869384928391906303ad5cef60e31b83528c8c8c60048601611e62565b03925af19081156113f857611a16611a10611a1c927f3cd37480027e3583365f7acfb552d017416b316c9bec58d3c722169ebcfbed8d95611a269561151257506105ee565b9061032c94939291611e2e565b9061032c9291611f4a612581565b61032c929190611f797f79af5a85184b45263c1b3721b9c8eb1becd98e55ade5a61da0fed18b90b832566123e0565b611f8561132b826105ee565b90813b1561031a576000611f9860405190565b636df6c7d560e11b815260048101869052928390602490829084905af19182156113f8576116776116879261167d927f4ec4ca1f307e20478f96cb1c545fe5430572a8db4931456f3828b649f641b1ef9561151257506105ee565b9061032c9291611f3c565b61201061034261034292611c15600090565b612757565b9061032c91612026610e1282610eab565b90611428916125ce565b9061032c91612015565b9061032c939291612049612581565b61032c939291906120797f6ac2c8b5322a929d300af17766189693ff6db6939ffe690430ad4720baf47d1c6123e0565b61208561132b826105ee565b90813b1561031a57600061209860405190565b928390634e6d8a7360e01b82528183816120b78b8b8b60048501611d78565b03925af19081156113f857611a16611a10611a1c927f1dd3b030dd7573079a398f70654588c91f23b82a2434fc5351cef1cbeda903e195611a269561151257506105ee565b9061032c93929161203a565b61032c90612123600080516020612d538339815191526123e0565b612186565b610342610e106109fd565b61213d6027610f3c565b7f353231317c50726f636573736f723a2073656375726974792064656c617920746020820152666f6f206c6f6e6760c81b604082015290565b610342612133565b610342612176565b610e7c7f7f63f876249f25f7856e620802edd09af38a83c9c6040fcdb371791540308311916121c36121b9610342612128565b82106110a161217e565b610415816006611048565b61032c90612108565b9061032c9695949392916121e9612581565b61032c9695949392919061220a600080516020612d738339815191526123e0565b61226c565b9194612261611e9a9298979561225060a09661224961032c9a61224260c08a019e60008b01906001600160a01b03169052565b6020890152565b6040870152565b6001600160801b0319166060850152565b61ffff166080830152565b9194959093929561227f61132b846105ee565b803b1561031a5786600087611eea82968c9661229a60405190565b9a8b9889978896636fb7e9c160e01b88526004880161220f565b9061032c9695949392916121d7565b9061032c9695949392916122d5612581565b61032c969594939291906122f6600080516020612d738339815191526123e0565b61234f565b9695939461032c956123396080956123326123469661232b8d97600060a08a019901906001600160a01b03169052565b60208d0152565b60408b0152565b88830360608a0152611272565b94019015159052565b9194959093929561236261132b846105ee565b803b1561031a5786600087611eea82968c9661237d60405190565b9a8b98899788966311de011760e31b8852600488016122fb565b9061032c9695949392916122c3565b637965db0b60e01b6001600160e01b03198216149081156123c5575090565b61034291506001600160e01b0319166301ffc9a760e01b1490565b61032c90339061276d565b9050519061032c82610aba565b9060208282031261031a57610342916123eb565b6124166037610f3c565b7f353230307c50726f636573736f723a207061796d656e74206163636f756e742060208201527f6e6f74206465706c6f79656420627920666163746f7279000000000000000000604082015290565b61034261240c565b610342612465565b602061249f9161248861132b600261112d565b604051938492839182916385bb392360e01b611921565b03915afa80156113f85761032c916000916124be575b506110a161246d565b6124e0915060203d6020116124e6575b6124d88183610ee0565b8101906123f8565b386124b5565b503d6124ce565b6125066125006103429263ffffffff1690565b60e01b90565b6001600160e01b03191690565b6001600160a01b0391821681529116602082015260608101929161032c9160400152565b9061257c9061256d61032c956004956125536323b872dd6124ed565b9361255d60405190565b9788956020870190815201612513565b60208201810382520383610ee0565b6127a9565b60025460ff1661258d57565b60405163d93c066560e01b8152600490fd5b6125a98282612831565b91826125b457505090565b6125c56103426125ca936001610e8a565b6128be565b5090565b6125d882826128f0565b91826125e357505090565b6125f46103426125ca936001610e8a565b61294c565b61260161296c565b61032c612629565b9060ff90610e27565b90612622610342610e4892151590565b8254612609565b61263560006002612612565b7f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa610e7c336108da565b61032c6125f9565b612671601b610f3c565b7f353130307c436f6d6d6f6e3a20616d6f756e74206973207a65726f0000000000602082015290565b610342612667565b61034261269a565b61032c906126bb61195660006109fd565b116110a16126a2565b6126cc612581565b61032c6126db60016002612612565b7f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258610e7c336108da565b61032c6126c4565b610342906109fd565b61037a6103426103429290565b61274d6127486103429361274360006127529561273e600090565b500190565b6129d1565b61270d565b612716565b6105e5565b61276860006103429261273e600090565b6129f0565b9061277f61277b8284611c2c565b1590565b612787575050565b61103a61279360405190565b63e2517d3f60e01b8152928392600484016115d8565b6127b56127bc916105ee565b9182612a04565b80516127cb61195660006109fd565b1415908161280d575b506127dc5750565b61103a906127e960405190565b635274afe760e01b8152918291600483016001600160a01b03909116815260200190565b61282b915080602061282061277b935190565b8183010191016123f8565b386127d4565b61283e61277b8383611c2c565b156128a55761285d6001612858846000611c488682610e8a565b612612565b61287161286b611677339390565b916105ee565b917f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d61289c60405190565b600090a4600190565b5050600090565b6103429081906001600160a01b031681565b906128eb6119566128e66128e16000610342966128d9600090565b5001946105e5565b6128ac565b6109fd565b612a71565b6128fa8282611c2c565b156128a55761291360006128588482611c488682610e8a565b61292161286b611677339390565b917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b61289c60405190565b906129676119566128e66128e16000610342966128d9600090565b612b3f565b61297b61277b60025460ff1690565b61298157565b604051638dfc202b60e01b8152600490fd5b634e487b7160e01b600052603260045260246000fd5b80548210156129cc576129c3600191600052602060002090565b91020190600090565b612993565b6103429160006129ea926129e3600090565b50016129a9565b90610b9d565b6000610342916129fe600090565b50015490565b61034291612a1260006109fd565b91612c23565b9160001960089290920291821b911b610e27565b9190612a3b610342610e489390565b908354612a18565b9081549168010000000000000000831015610f025782612a6b91600161032c950181556129a9565b90612a2c565b612a7e61277b8383612c88565b156128a557612aab91612aa6906001612a9f84612a9b8482612a43565b5490565b9301610e8a565b611048565b600190565b634e487b7160e01b600052601160045260246000fd5b91908203918211612ad357565b612ab0565b634e487b7160e01b600052603160045260246000fd5b61032c91600091612a2c565b80548015612b1d576000190190612b1a612b1483836129a9565b90612aee565b55565b612ad8565b9190612a3b610342610e48936109fd565b61032c91600091612b22565b90612b55612b508260018501610e8a565b610ea1565b612b5f60006109fd565b8114612bfc57612bc461034292600092612bb995612bbe6001978893612b8d612b87866109fd565b82612ac6565b88850191612bab612b9c845490565b612ba5896109fd565b90612ac6565b808303612bc9575b50505090565b612afa565b01610e8a565b612b33565b612a6b612bec612bf494612be36129ea612aa695896129a9565b928391886129a9565b888801610e8a565b388080612bb3565b505050600090565b3d15612c1e57612c133d610f3c565b903d6000602084013e565b606090565b91612c2d306105ee565b81813110612c57575060008281926020610342969551920190855af1612c51612c04565b91612cb3565b61103a90612c6460405190565b63cd78605960e01b8152918291600483016001600160a01b03909116815260200190565b612ca1916001612b5092612c9a600090565b5001610e8a565b612cae61195660006109fd565b141590565b90612cbe5750612d23565b8151612ccd61195660006109fd565b1480612d0d575b612cdc575090565b61103a90612ce960405190565b639996b31560e01b8152918291600483016001600160a01b03909116815260200190565b50803b612d1d61195660006109fd565b14612cd4565b8051612d3261195660006109fd565b1115612d4057805190602001fd5b604051630a12f52160e11b8152600490fdfefa658e47c75a6eb0e27156b2f72e1b295c98b53e8a1d238d7ca6ea6345b34880a567244cad934c87b7f7e15b15dd0e75f95a76afd1916f8eab4a391410d6f278a26469706673582212202e2530549c24438ec28dea3d0ac5bc4a94ae7752c7cb46a83d147d6cd4df5ffd64736f6c63430008160033"; + + private static String librariesLinkedBinary; public static final String FUNC_AUTHORIZATION_PROCESSOR_ROLE = "AUTHORIZATION_PROCESSOR_ROLE"; public static final String FUNC_BALANCE_VERIFIER_ROLE = "BALANCE_VERIFIER_ROLE"; + public static final String FUNC_CARD_CONFIRMER_ROLE = "CARD_CONFIRMER_ROLE"; + public static final String FUNC_DEBT_PROCESSOR_ROLE = "DEBT_PROCESSOR_ROLE"; public static final String FUNC_DEFAULT_ADMIN_ROLE = "DEFAULT_ADMIN_ROLE"; - public static final String FUNC_FIXED_FEE_SETTER_ROLE = "FIXED_FEE_SETTER_ROLE"; - public static final String FUNC_PAUSER_ROLE = "PAUSER_ROLE"; - public static final String FUNC_PAYMENT_ACCOUNT_SETTER_ROLE = "PAYMENT_ACCOUNT_SETTER_ROLE"; + public static final String FUNC_PAYMENT_ACCOUNT_DEPLOYER_ROLE = "PAYMENT_ACCOUNT_DEPLOYER_ROLE"; + + public static final String FUNC_PAYMENT_ACCOUNT_PROPERTY_SETTER_ROLE = "PAYMENT_ACCOUNT_PROPERTY_SETTER_ROLE"; public static final String FUNC_PROPERTY_SETTER_ROLE = "PROPERTY_SETTER_ROLE"; @@ -61,9 +69,9 @@ class TangemBridgeProcessor extends Contract { public static final String FUNC_SETTLEMENT_PROCESSOR_ROLE = "SETTLEMENT_PROCESSOR_ROLE"; - public static final String FUNC_FIXEDFEE = "fixedFee"; + public static final String FUNC_WITHDRAWAL_PROCESSOR_ROLE = "WITHDRAWAL_PROCESSOR_ROLE"; - public static final String FUNC_GETPAYMENTACCOUNT = "getPaymentAccount"; + public static final String FUNC_DEPLOYPAYMENTACCOUNT = "deployPaymentAccount"; public static final String FUNC_GETROLEADMIN = "getRoleAdmin"; @@ -77,6 +85,8 @@ class TangemBridgeProcessor extends Contract { public static final String FUNC_INCREASEVERIFIEDBALANCEFOR = "increaseVerifiedBalanceFor"; + public static final String FUNC_ISCARDCONFIRMER = "isCardConfirmer"; + public static final String FUNC_PAUSE = "pause"; public static final String FUNC_PAUSED = "paused"; @@ -87,13 +97,13 @@ class TangemBridgeProcessor extends Contract { public static final String FUNC_PAYMENTTOKEN = "paymentToken"; - public static final String FUNC_PROCESSAUTHORIZATION = "processAuthorization"; - public static final String FUNC_PROCESSAUTHORIZATIONCHANGE = "processAuthorizationChange"; - public static final String FUNC_PROCESSAUTHORIZATIONNOOTP = "processAuthorizationNoOtp"; + public static final String FUNC_PROCESSDEBT = "processDebt"; - public static final String FUNC_PROCESSPENDINGREFUND = "processPendingRefund"; + public static final String FUNC_PROCESSNOCONFIRMATIONAUTHORIZATION = "processNoConfirmationAuthorization"; + + public static final String FUNC_PROCESSOTPAUTHORIZATION = "processOtpAuthorization"; public static final String FUNC_PROCESSREFUND = "processRefund"; @@ -101,19 +111,21 @@ class TangemBridgeProcessor extends Contract { public static final String FUNC_PROCESSSETTLEMENT = "processSettlement"; + public static final String FUNC_PROCESSSIGNATUREAUTHORIZATION = "processSignatureAuthorization"; + + public static final String FUNC_PROCESSUNSETTLEDTRANSACTION = "processUnsettledTransaction"; + + public static final String FUNC_PROCESSWITHDRAWAL = "processWithdrawal"; + public static final String FUNC_REFUNDACCOUNT = "refundAccount"; public static final String FUNC_RENOUNCEROLE = "renounceRole"; public static final String FUNC_REVOKEROLE = "revokeRole"; - public static final String FUNC_SAVEPENDINGREFUND = "savePendingRefund"; - public static final String FUNC_SECURITYDELAY = "securityDelay"; - public static final String FUNC_SETFIXEDFEE = "setFixedFee"; - - public static final String FUNC_SETPAYMENTACCOUNT = "setPaymentAccount"; + public static final String FUNC_SETAUTHLIMITMARGINFOR = "setAuthLimitMarginFor"; public static final String FUNC_SETPAYMENTRECEIVER = "setPaymentReceiver"; @@ -121,214 +133,312 @@ class TangemBridgeProcessor extends Contract { public static final String FUNC_SETSECURITYDELAY = "setSecurityDelay"; - public static final String FUNC_SETSETTLEMENTPERIOD = "setSettlementPeriod"; - public static final String FUNC_SETVERIFIEDBALANCEFOR = "setVerifiedBalanceFor"; - public static final String FUNC_SETTLEMENTPERIOD = "settlementPeriod"; - public static final String FUNC_SUPPORTSINTERFACE = "supportsInterface"; public static final String FUNC_UNPAUSE = "unpause"; public static final String FUNC_WRITEOFFDEBT = "writeOffDebt"; + public static final Event AUTHLIMITMARGINSETFOR_EVENT = new Event("AuthLimitMarginSetFor", + Arrays.>asList(new TypeReference

(true) {}, new TypeReference() {})); + ; + public static final Event AUTHORIZATIONCHANGEPROCESSED_EVENT = new Event("AuthorizationChangeProcessed", - Arrays.asList(new TypeReference
(true) { - }, new TypeReference(true) { - }, new TypeReference() { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference
(true) {}, new TypeReference
(true) {}, new TypeReference(true) {}, new TypeReference() {})); + ; public static final Event AUTHORIZATIONPROCESSED_EVENT = new Event("AuthorizationProcessed", - Arrays.asList(new TypeReference
(true) { - }, new TypeReference(true) { - }, new TypeReference() { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference
(true) {}, new TypeReference
(true) {}, new TypeReference(true) {}, new TypeReference() {})); + ; + + public static final Event DEBTPROCESSED_EVENT = new Event("DebtProcessed", + Arrays.>asList(new TypeReference
(true) {}, new TypeReference
(true) {}, new TypeReference() {})); + ; public static final Event DEBTWRITEOFFPROCESSED_EVENT = new Event("DebtWriteOffProcessed", - Arrays.asList(new TypeReference
(true) { - }, new TypeReference() { - })); - - public static final Event FIXEDFEESET_EVENT = new Event("FixedFeeSet", - List.of(new TypeReference() { - })); + Arrays.>asList(new TypeReference
(true) {}, new TypeReference
(true) {}, new TypeReference() {})); + ; public static final Event PAUSED_EVENT = new Event("Paused", - List.of(new TypeReference
() { - })); + Arrays.>asList(new TypeReference
() {})); + ; - public static final Event PAYMENTACCOUNTSET_EVENT = new Event("PaymentAccountSet", - Arrays.asList(new TypeReference
(true) { - }, new TypeReference
() { - })); + public static final Event PAYMENTACCOUNTDEPLOYED_EVENT = new Event("PaymentAccountDeployed", + Arrays.>asList(new TypeReference
(true) {}, new TypeReference
() {}, new TypeReference
() {})); + ; public static final Event PAYMENTRECEIVERSET_EVENT = new Event("PaymentReceiverSet", - List.of(new TypeReference
() { - })); - - public static final Event PENDINGREFUNDPROCESSED_EVENT = new Event("PendingRefundProcessed", - Arrays.asList(new TypeReference
(true) { - }, new TypeReference(true) { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference
() {})); + ; public static final Event REFUNDACCOUNTSET_EVENT = new Event("RefundAccountSet", - List.of(new TypeReference
() { - })); + Arrays.>asList(new TypeReference
() {})); + ; public static final Event REFUNDPROCESSED_EVENT = new Event("RefundProcessed", - Arrays.asList(new TypeReference
(true) { - }, new TypeReference(true) { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference
(true) {}, new TypeReference
(true) {}, new TypeReference(true) {}, new TypeReference() {})); + ; public static final Event ROLEADMINCHANGED_EVENT = new Event("RoleAdminChanged", - Arrays.asList(new TypeReference(true) { - }, new TypeReference(true) { - }, new TypeReference(true) { - })); + Arrays.>asList(new TypeReference(true) {}, new TypeReference(true) {}, new TypeReference(true) {})); + ; public static final Event ROLEGRANTED_EVENT = new Event("RoleGranted", - Arrays.asList(new TypeReference(true) { - }, new TypeReference
(true) { - }, new TypeReference
(true) { - })); + Arrays.>asList(new TypeReference(true) {}, new TypeReference
(true) {}, new TypeReference
(true) {})); + ; public static final Event ROLEREVOKED_EVENT = new Event("RoleRevoked", - Arrays.asList(new TypeReference(true) { - }, new TypeReference
(true) { - }, new TypeReference
(true) { - })); - - public static final Event SAVEREFUNDPROCESSED_EVENT = new Event("SaveRefundProcessed", - Arrays.asList(new TypeReference
(true) { - }, new TypeReference(true) { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference(true) {}, new TypeReference
(true) {}, new TypeReference
(true) {})); + ; public static final Event SECURITYDELAYSET_EVENT = new Event("SecurityDelaySet", - List.of(new TypeReference() { - })); + Arrays.>asList(new TypeReference() {})); + ; public static final Event SETTLEMENTPERIODSET_EVENT = new Event("SettlementPeriodSet", - List.of(new TypeReference() { - })); + Arrays.>asList(new TypeReference() {})); + ; public static final Event SETTLEMENTPROCESSED_EVENT = new Event("SettlementProcessed", - Arrays.asList(new TypeReference
(true) { - }, new TypeReference(true) { - }, new TypeReference() { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference
(true) {}, new TypeReference
(true) {}, new TypeReference(true) {}, new TypeReference() {})); + ; public static final Event UNPAUSED_EVENT = new Event("Unpaused", - List.of(new TypeReference
() { - })); + Arrays.>asList(new TypeReference
() {})); + ; + + public static final Event UNSETTLEDTRANSACTIONPROCESSED_EVENT = new Event("UnsettledTransactionProcessed", + Arrays.>asList(new TypeReference
(true) {}, new TypeReference
(true) {}, new TypeReference() {})); + ; public static final Event VERIFIEDBALANCEINCREASEDFOR_EVENT = new Event("VerifiedBalanceIncreasedFor", - Arrays.asList(new TypeReference
(true) { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference
(true) {}, new TypeReference() {})); + ; public static final Event VERIFIEDBALANCESETFOR_EVENT = new Event("VerifiedBalanceSetFor", - Arrays.asList(new TypeReference
(true) { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference
(true) {}, new TypeReference() {})); + ; + + public static final Event WITHDRAWALPROCESSED_EVENT = new Event("WithdrawalProcessed", + Arrays.>asList(new TypeReference
(true) {})); + ; @Deprecated - protected TangemBridgeProcessor(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + protected TangemBridgeProcessor(String contractAddress, Web3j web3j, Credentials credentials, + BigInteger gasPrice, BigInteger gasLimit) { super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); } - protected TangemBridgeProcessor(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + protected TangemBridgeProcessor(String contractAddress, Web3j web3j, Credentials credentials, + ContractGasProvider contractGasProvider) { super(BINARY, contractAddress, web3j, credentials, contractGasProvider); } @Deprecated - protected TangemBridgeProcessor(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + protected TangemBridgeProcessor(String contractAddress, Web3j web3j, + TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); } - protected TangemBridgeProcessor(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + protected TangemBridgeProcessor(String contractAddress, Web3j web3j, + TransactionManager transactionManager, ContractGasProvider contractGasProvider) { super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); } - public static List getAuthorizationChangeProcessedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(AUTHORIZATIONCHANGEPROCESSED_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - AuthorizationChangeProcessedEventResponse typedResponse = new AuthorizationChangeProcessedEventResponse(); + public static List getAuthLimitMarginSetForEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(AUTHLIMITMARGINSETFOR_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + AuthLimitMarginSetForEventResponse typedResponse = new AuthLimitMarginSetForEventResponse(); typedResponse.log = eventValues.getLog(); - typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.fee = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.authLimitMargin = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); responses.add(typedResponse); } return responses; } - public static AuthorizationChangeProcessedEventResponse getAuthorizationChangeProcessedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(AUTHORIZATIONCHANGEPROCESSED_EVENT, log); - AuthorizationChangeProcessedEventResponse typedResponse = new AuthorizationChangeProcessedEventResponse(); + public static AuthLimitMarginSetForEventResponse getAuthLimitMarginSetForEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(AUTHLIMITMARGINSETFOR_EVENT, log); + AuthLimitMarginSetForEventResponse typedResponse = new AuthLimitMarginSetForEventResponse(); typedResponse.log = log; - typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.fee = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.authLimitMargin = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); return typedResponse; } - public static AuthorizationProcessedEventResponse getAuthorizationProcessedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(AUTHORIZATIONPROCESSED_EVENT, log); + public Flowable authLimitMarginSetForEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getAuthLimitMarginSetForEventFromLog(log)); + } + + public Flowable authLimitMarginSetForEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(AUTHLIMITMARGINSETFOR_EVENT)); + return authLimitMarginSetForEventFlowable(filter); + } + + public static List getAuthorizationChangeProcessedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(AUTHORIZATIONCHANGEPROCESSED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + AuthorizationChangeProcessedEventResponse typedResponse = new AuthorizationChangeProcessedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(2).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static AuthorizationChangeProcessedEventResponse getAuthorizationChangeProcessedEventFromLog( + Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(AUTHORIZATIONCHANGEPROCESSED_EVENT, log); + AuthorizationChangeProcessedEventResponse typedResponse = new AuthorizationChangeProcessedEventResponse(); + typedResponse.log = log; + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(2).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable authorizationChangeProcessedEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getAuthorizationChangeProcessedEventFromLog(log)); + } + + public Flowable authorizationChangeProcessedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(AUTHORIZATIONCHANGEPROCESSED_EVENT)); + return authorizationChangeProcessedEventFlowable(filter); + } + + public static List getAuthorizationProcessedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(AUTHORIZATIONPROCESSED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + AuthorizationProcessedEventResponse typedResponse = new AuthorizationProcessedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(2).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static AuthorizationProcessedEventResponse getAuthorizationProcessedEventFromLog( + Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(AUTHORIZATIONPROCESSED_EVENT, log); AuthorizationProcessedEventResponse typedResponse = new AuthorizationProcessedEventResponse(); typedResponse.log = log; - typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(2).getValue(); typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.fee = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); return typedResponse; } + public Flowable authorizationProcessedEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getAuthorizationProcessedEventFromLog(log)); + } + + public Flowable authorizationProcessedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(AUTHORIZATIONPROCESSED_EVENT)); + return authorizationProcessedEventFlowable(filter); + } + + public static List getDebtProcessedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(DEBTPROCESSED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + DebtProcessedEventResponse typedResponse = new DebtProcessedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static DebtProcessedEventResponse getDebtProcessedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(DEBTPROCESSED_EVENT, log); + DebtProcessedEventResponse typedResponse = new DebtProcessedEventResponse(); + typedResponse.log = log; + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable debtProcessedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getDebtProcessedEventFromLog(log)); + } + + public Flowable debtProcessedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(DEBTPROCESSED_EVENT)); + return debtProcessedEventFlowable(filter); + } + + public static List getDebtWriteOffProcessedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(DEBTWRITEOFFPROCESSED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + DebtWriteOffProcessedEventResponse typedResponse = new DebtWriteOffProcessedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + public static DebtWriteOffProcessedEventResponse getDebtWriteOffProcessedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(DEBTWRITEOFFPROCESSED_EVENT, log); + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(DEBTWRITEOFFPROCESSED_EVENT, log); DebtWriteOffProcessedEventResponse typedResponse = new DebtWriteOffProcessedEventResponse(); typedResponse.log = log; - typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); return typedResponse; } - public static List getAuthorizationProcessedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(AUTHORIZATIONPROCESSED_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - AuthorizationProcessedEventResponse typedResponse = new AuthorizationProcessedEventResponse(); - typedResponse.log = eventValues.getLog(); - typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.fee = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); - responses.add(typedResponse); - } - return responses; + public Flowable debtWriteOffProcessedEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getDebtWriteOffProcessedEventFromLog(log)); } - public static FixedFeeSetEventResponse getFixedFeeSetEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(FIXEDFEESET_EVENT, log); - FixedFeeSetEventResponse typedResponse = new FixedFeeSetEventResponse(); - typedResponse.log = log; - typedResponse.fixedFee = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; + public Flowable debtWriteOffProcessedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(DEBTWRITEOFFPROCESSED_EVENT)); + return debtWriteOffProcessedEventFlowable(filter); } public static List getPausedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(PAUSED_EVENT, transactionReceipt); + List valueList = staticExtractEventParametersWithLog(PAUSED_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { PausedEventResponse typedResponse = new PausedEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.account = (String) eventValues.getNonIndexedValues().get(0).getValue(); @@ -338,177 +448,67 @@ class TangemBridgeProcessor extends Contract { } public static PausedEventResponse getPausedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PAUSED_EVENT, log); + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PAUSED_EVENT, log); PausedEventResponse typedResponse = new PausedEventResponse(); typedResponse.log = log; typedResponse.account = (String) eventValues.getNonIndexedValues().get(0).getValue(); return typedResponse; } - public static List getDebtWriteOffProcessedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(DEBTWRITEOFFPROCESSED_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - DebtWriteOffProcessedEventResponse typedResponse = new DebtWriteOffProcessedEventResponse(); + public Flowable pausedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getPausedEventFromLog(log)); + } + + public Flowable pausedEventFlowable(DefaultBlockParameter startBlock, + DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(PAUSED_EVENT)); + return pausedEventFlowable(filter); + } + + public static List getPaymentAccountDeployedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(PAYMENTACCOUNTDEPLOYED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + PaymentAccountDeployedEventResponse typedResponse = new PaymentAccountDeployedEventResponse(); typedResponse.log = eventValues.getLog(); - typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.owner = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.cardAddress = (String) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.paymentAccount = (String) eventValues.getNonIndexedValues().get(1).getValue(); responses.add(typedResponse); } return responses; } - public static PaymentAccountSetEventResponse getPaymentAccountSetEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PAYMENTACCOUNTSET_EVENT, log); - PaymentAccountSetEventResponse typedResponse = new PaymentAccountSetEventResponse(); + public static PaymentAccountDeployedEventResponse getPaymentAccountDeployedEventFromLog( + Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PAYMENTACCOUNTDEPLOYED_EVENT, log); + PaymentAccountDeployedEventResponse typedResponse = new PaymentAccountDeployedEventResponse(); typedResponse.log = log; - typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.paymentAccount = (String) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.owner = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.cardAddress = (String) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.paymentAccount = (String) eventValues.getNonIndexedValues().get(1).getValue(); return typedResponse; } - public static PaymentReceiverSetEventResponse getPaymentReceiverSetEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PAYMENTRECEIVERSET_EVENT, log); - PaymentReceiverSetEventResponse typedResponse = new PaymentReceiverSetEventResponse(); - typedResponse.log = log; - typedResponse.paymentReceiver = (String) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; + public Flowable paymentAccountDeployedEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getPaymentAccountDeployedEventFromLog(log)); } - public static RefundAccountSetEventResponse getRefundAccountSetEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(REFUNDACCOUNTSET_EVENT, log); - RefundAccountSetEventResponse typedResponse = new RefundAccountSetEventResponse(); - typedResponse.log = log; - typedResponse.refundAccount = (String) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; + public Flowable paymentAccountDeployedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(PAYMENTACCOUNTDEPLOYED_EVENT)); + return paymentAccountDeployedEventFlowable(filter); } - public static List getFixedFeeSetEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(FIXEDFEESET_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - FixedFeeSetEventResponse typedResponse = new FixedFeeSetEventResponse(); - typedResponse.log = eventValues.getLog(); - typedResponse.fixedFee = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - responses.add(typedResponse); - } - return responses; - } - - public static RefundProcessedEventResponse getRefundProcessedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(REFUNDPROCESSED_EVENT, log); - RefundProcessedEventResponse typedResponse = new RefundProcessedEventResponse(); - typedResponse.log = log; - typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; - } - - public static RoleAdminChangedEventResponse getRoleAdminChangedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(ROLEADMINCHANGED_EVENT, log); - RoleAdminChangedEventResponse typedResponse = new RoleAdminChangedEventResponse(); - typedResponse.log = log; - typedResponse.role = (byte[]) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.previousAdminRole = (byte[]) eventValues.getIndexedValues().get(1).getValue(); - typedResponse.newAdminRole = (byte[]) eventValues.getIndexedValues().get(2).getValue(); - return typedResponse; - } - - public static RoleGrantedEventResponse getRoleGrantedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(ROLEGRANTED_EVENT, log); - RoleGrantedEventResponse typedResponse = new RoleGrantedEventResponse(); - typedResponse.log = log; - typedResponse.role = (byte[]) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.account = (String) eventValues.getIndexedValues().get(1).getValue(); - typedResponse.sender = (String) eventValues.getIndexedValues().get(2).getValue(); - return typedResponse; - } - - public static RoleRevokedEventResponse getRoleRevokedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(ROLEREVOKED_EVENT, log); - RoleRevokedEventResponse typedResponse = new RoleRevokedEventResponse(); - typedResponse.log = log; - typedResponse.role = (byte[]) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.account = (String) eventValues.getIndexedValues().get(1).getValue(); - typedResponse.sender = (String) eventValues.getIndexedValues().get(2).getValue(); - return typedResponse; - } - - public static SaveRefundProcessedEventResponse getSaveRefundProcessedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(SAVEREFUNDPROCESSED_EVENT, log); - SaveRefundProcessedEventResponse typedResponse = new SaveRefundProcessedEventResponse(); - typedResponse.log = log; - typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; - } - - public static SecurityDelaySetEventResponse getSecurityDelaySetEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(SECURITYDELAYSET_EVENT, log); - SecurityDelaySetEventResponse typedResponse = new SecurityDelaySetEventResponse(); - typedResponse.log = log; - typedResponse.securityDelay = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; - } - - public static SettlementPeriodSetEventResponse getSettlementPeriodSetEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(SETTLEMENTPERIODSET_EVENT, log); - SettlementPeriodSetEventResponse typedResponse = new SettlementPeriodSetEventResponse(); - typedResponse.log = log; - typedResponse.settlementPeriod = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; - } - - public static List getPaymentAccountSetEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(PAYMENTACCOUNTSET_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - PaymentAccountSetEventResponse typedResponse = new PaymentAccountSetEventResponse(); - typedResponse.log = eventValues.getLog(); - typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.paymentAccount = (String) eventValues.getNonIndexedValues().get(0).getValue(); - responses.add(typedResponse); - } - return responses; - } - - public static SettlementProcessedEventResponse getSettlementProcessedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(SETTLEMENTPROCESSED_EVENT, log); - SettlementProcessedEventResponse typedResponse = new SettlementProcessedEventResponse(); - typedResponse.log = log; - typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.fee = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); - return typedResponse; - } - - public static List getUnpausedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(UNPAUSED_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - UnpausedEventResponse typedResponse = new UnpausedEventResponse(); - typedResponse.log = eventValues.getLog(); - typedResponse.account = (String) eventValues.getNonIndexedValues().get(0).getValue(); - responses.add(typedResponse); - } - return responses; - } - - public static UnpausedEventResponse getUnpausedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(UNPAUSED_EVENT, log); - UnpausedEventResponse typedResponse = new UnpausedEventResponse(); - typedResponse.log = log; - typedResponse.account = (String) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; - } - - public static List getPaymentReceiverSetEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(PAYMENTRECEIVERSET_EVENT, transactionReceipt); + public static List getPaymentReceiverSetEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(PAYMENTRECEIVERSET_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { PaymentReceiverSetEventResponse typedResponse = new PaymentReceiverSetEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.paymentReceiver = (String) eventValues.getNonIndexedValues().get(0).getValue(); @@ -517,61 +517,31 @@ class TangemBridgeProcessor extends Contract { return responses; } - public static VerifiedBalanceIncreasedForEventResponse getVerifiedBalanceIncreasedForEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(VERIFIEDBALANCEINCREASEDFOR_EVENT, log); - VerifiedBalanceIncreasedForEventResponse typedResponse = new VerifiedBalanceIncreasedForEventResponse(); + public static PaymentReceiverSetEventResponse getPaymentReceiverSetEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PAYMENTRECEIVERSET_EVENT, log); + PaymentReceiverSetEventResponse typedResponse = new PaymentReceiverSetEventResponse(); typedResponse.log = log; - typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.increase = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.paymentReceiver = (String) eventValues.getNonIndexedValues().get(0).getValue(); return typedResponse; } - @Deprecated - public static TangemBridgeProcessor load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { - return new TangemBridgeProcessor(contractAddress, web3j, credentials, gasPrice, gasLimit); + public Flowable paymentReceiverSetEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getPaymentReceiverSetEventFromLog(log)); } - @Deprecated - public static TangemBridgeProcessor load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { - return new TangemBridgeProcessor(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + public Flowable paymentReceiverSetEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(PAYMENTRECEIVERSET_EVENT)); + return paymentReceiverSetEventFlowable(filter); } - public static List getPendingRefundProcessedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(PENDINGREFUNDPROCESSED_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - PendingRefundProcessedEventResponse typedResponse = new PendingRefundProcessedEventResponse(); - typedResponse.log = eventValues.getLog(); - typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - responses.add(typedResponse); - } - return responses; - } - - public static PendingRefundProcessedEventResponse getPendingRefundProcessedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PENDINGREFUNDPROCESSED_EVENT, log); - PendingRefundProcessedEventResponse typedResponse = new PendingRefundProcessedEventResponse(); - typedResponse.log = log; - typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; - } - - public static TangemBridgeProcessor load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { - return new TangemBridgeProcessor(contractAddress, web3j, credentials, contractGasProvider); - } - - public static TangemBridgeProcessor load(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { - return new TangemBridgeProcessor(contractAddress, web3j, transactionManager, contractGasProvider); - } - - public static List getRefundAccountSetEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(REFUNDACCOUNTSET_EVENT, transactionReceipt); + public static List getRefundAccountSetEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(REFUNDACCOUNTSET_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { RefundAccountSetEventResponse typedResponse = new RefundAccountSetEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.refundAccount = (String) eventValues.getNonIndexedValues().get(0).getValue(); @@ -580,72 +550,68 @@ class TangemBridgeProcessor extends Contract { return responses; } - public static RemoteCall deploy(Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider, String paymentReceiver_, String refundAccount_, String paymentAccountFactory_, String paymentToken_, BigInteger securityDelay_) { - String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new Address(160, paymentReceiver_), - new Address(160, refundAccount_), - new Address(160, paymentAccountFactory_), - new Address(160, paymentToken_), - new Uint256(securityDelay_))); - return deployRemoteCall(TangemBridgeProcessor.class, web3j, credentials, contractGasProvider, BINARY, encodedConstructor); + public static RefundAccountSetEventResponse getRefundAccountSetEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(REFUNDACCOUNTSET_EVENT, log); + RefundAccountSetEventResponse typedResponse = new RefundAccountSetEventResponse(); + typedResponse.log = log; + typedResponse.refundAccount = (String) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; } - public static RemoteCall deploy(Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider, String paymentReceiver_, String refundAccount_, String paymentAccountFactory_, String paymentToken_, BigInteger securityDelay_) { - String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new Address(160, paymentReceiver_), - new Address(160, refundAccount_), - new Address(160, paymentAccountFactory_), - new Address(160, paymentToken_), - new Uint256(securityDelay_))); - return deployRemoteCall(TangemBridgeProcessor.class, web3j, transactionManager, contractGasProvider, BINARY, encodedConstructor); + public Flowable refundAccountSetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getRefundAccountSetEventFromLog(log)); } - @Deprecated - public static RemoteCall deploy(Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit, String paymentReceiver_, String refundAccount_, String paymentAccountFactory_, String paymentToken_, BigInteger securityDelay_) { - String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new Address(160, paymentReceiver_), - new Address(160, refundAccount_), - new Address(160, paymentAccountFactory_), - new Address(160, paymentToken_), - new Uint256(securityDelay_))); - return deployRemoteCall(TangemBridgeProcessor.class, web3j, credentials, gasPrice, gasLimit, BINARY, encodedConstructor); + public Flowable refundAccountSetEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(REFUNDACCOUNTSET_EVENT)); + return refundAccountSetEventFlowable(filter); } - public static List getRefundProcessedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(REFUNDPROCESSED_EVENT, transactionReceipt); + public static List getRefundProcessedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(REFUNDPROCESSED_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { RefundProcessedEventResponse typedResponse = new RefundProcessedEventResponse(); typedResponse.log = eventValues.getLog(); - typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(2).getValue(); typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); responses.add(typedResponse); } return responses; } - @Deprecated - public static RemoteCall deploy(Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit, String paymentReceiver_, String refundAccount_, String paymentAccountFactory_, String paymentToken_, BigInteger securityDelay_) { - String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new Address(160, paymentReceiver_), - new Address(160, refundAccount_), - new Address(160, paymentAccountFactory_), - new Address(160, paymentToken_), - new Uint256(securityDelay_))); - return deployRemoteCall(TangemBridgeProcessor.class, web3j, transactionManager, gasPrice, gasLimit, BINARY, encodedConstructor); + public static RefundProcessedEventResponse getRefundProcessedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(REFUNDPROCESSED_EVENT, log); + RefundProcessedEventResponse typedResponse = new RefundProcessedEventResponse(); + typedResponse.log = log; + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(2).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; } - public Flowable authorizationChangeProcessedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getAuthorizationChangeProcessedEventFromLog(log)); + public Flowable refundProcessedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getRefundProcessedEventFromLog(log)); } - public Flowable authorizationChangeProcessedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public Flowable refundProcessedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(AUTHORIZATIONCHANGEPROCESSED_EVENT)); - return authorizationChangeProcessedEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(REFUNDPROCESSED_EVENT)); + return refundProcessedEventFlowable(filter); } - public static List getRoleAdminChangedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(ROLEADMINCHANGED_EVENT, transactionReceipt); + public static List getRoleAdminChangedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(ROLEADMINCHANGED_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { RoleAdminChangedEventResponse typedResponse = new RoleAdminChangedEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.role = (byte[]) eventValues.getIndexedValues().get(0).getValue(); @@ -656,24 +622,32 @@ class TangemBridgeProcessor extends Contract { return responses; } - public Flowable authorizationProcessedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getAuthorizationProcessedEventFromLog(log)); + public static RoleAdminChangedEventResponse getRoleAdminChangedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(ROLEADMINCHANGED_EVENT, log); + RoleAdminChangedEventResponse typedResponse = new RoleAdminChangedEventResponse(); + typedResponse.log = log; + typedResponse.role = (byte[]) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.previousAdminRole = (byte[]) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.newAdminRole = (byte[]) eventValues.getIndexedValues().get(2).getValue(); + return typedResponse; } - public Flowable authorizationProcessedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public Flowable roleAdminChangedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getRoleAdminChangedEventFromLog(log)); + } + + public Flowable roleAdminChangedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(AUTHORIZATIONPROCESSED_EVENT)); - return authorizationProcessedEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(ROLEADMINCHANGED_EVENT)); + return roleAdminChangedEventFlowable(filter); } - public Flowable debtWriteOffProcessedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getDebtWriteOffProcessedEventFromLog(log)); - } - - public static List getRoleGrantedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(ROLEGRANTED_EVENT, transactionReceipt); + public static List getRoleGrantedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(ROLEGRANTED_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { RoleGrantedEventResponse typedResponse = new RoleGrantedEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.role = (byte[]) eventValues.getIndexedValues().get(0).getValue(); @@ -684,26 +658,32 @@ class TangemBridgeProcessor extends Contract { return responses; } - public Flowable debtWriteOffProcessedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public static RoleGrantedEventResponse getRoleGrantedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(ROLEGRANTED_EVENT, log); + RoleGrantedEventResponse typedResponse = new RoleGrantedEventResponse(); + typedResponse.log = log; + typedResponse.role = (byte[]) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.account = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.sender = (String) eventValues.getIndexedValues().get(2).getValue(); + return typedResponse; + } + + public Flowable roleGrantedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getRoleGrantedEventFromLog(log)); + } + + public Flowable roleGrantedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(DEBTWRITEOFFPROCESSED_EVENT)); - return debtWriteOffProcessedEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(ROLEGRANTED_EVENT)); + return roleGrantedEventFlowable(filter); } - public Flowable fixedFeeSetEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getFixedFeeSetEventFromLog(log)); - } - - public Flowable fixedFeeSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(FIXEDFEESET_EVENT)); - return fixedFeeSetEventFlowable(filter); - } - - public static List getRoleRevokedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(ROLEREVOKED_EVENT, transactionReceipt); + public static List getRoleRevokedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(ROLEREVOKED_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { RoleRevokedEventResponse typedResponse = new RoleRevokedEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.role = (byte[]) eventValues.getIndexedValues().get(0).getValue(); @@ -714,54 +694,32 @@ class TangemBridgeProcessor extends Contract { return responses; } - public Flowable pausedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getPausedEventFromLog(log)); + public static RoleRevokedEventResponse getRoleRevokedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(ROLEREVOKED_EVENT, log); + RoleRevokedEventResponse typedResponse = new RoleRevokedEventResponse(); + typedResponse.log = log; + typedResponse.role = (byte[]) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.account = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.sender = (String) eventValues.getIndexedValues().get(2).getValue(); + return typedResponse; } - public Flowable pausedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public Flowable roleRevokedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getRoleRevokedEventFromLog(log)); + } + + public Flowable roleRevokedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(PAUSED_EVENT)); - return pausedEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(ROLEREVOKED_EVENT)); + return roleRevokedEventFlowable(filter); } - public Flowable paymentAccountSetEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getPaymentAccountSetEventFromLog(log)); - } - - public static List getSaveRefundProcessedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(SAVEREFUNDPROCESSED_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - SaveRefundProcessedEventResponse typedResponse = new SaveRefundProcessedEventResponse(); - typedResponse.log = eventValues.getLog(); - typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - responses.add(typedResponse); - } - return responses; - } - - public Flowable paymentAccountSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(PAYMENTACCOUNTSET_EVENT)); - return paymentAccountSetEventFlowable(filter); - } - - public Flowable paymentReceiverSetEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getPaymentReceiverSetEventFromLog(log)); - } - - public Flowable paymentReceiverSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(PAYMENTRECEIVERSET_EVENT)); - return paymentReceiverSetEventFlowable(filter); - } - - public static List getSecurityDelaySetEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(SECURITYDELAYSET_EVENT, transactionReceipt); + public static List getSecurityDelaySetEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(SECURITYDELAYSET_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { SecurityDelaySetEventResponse typedResponse = new SecurityDelaySetEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.securityDelay = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); @@ -770,24 +728,30 @@ class TangemBridgeProcessor extends Contract { return responses; } - public Flowable pendingRefundProcessedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getPendingRefundProcessedEventFromLog(log)); + public static SecurityDelaySetEventResponse getSecurityDelaySetEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(SECURITYDELAYSET_EVENT, log); + SecurityDelaySetEventResponse typedResponse = new SecurityDelaySetEventResponse(); + typedResponse.log = log; + typedResponse.securityDelay = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; } - public Flowable pendingRefundProcessedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public Flowable securityDelaySetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getSecurityDelaySetEventFromLog(log)); + } + + public Flowable securityDelaySetEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(PENDINGREFUNDPROCESSED_EVENT)); - return pendingRefundProcessedEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(SECURITYDELAYSET_EVENT)); + return securityDelaySetEventFlowable(filter); } - public Flowable refundAccountSetEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getRefundAccountSetEventFromLog(log)); - } - - public static List getSettlementPeriodSetEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(SETTLEMENTPERIODSET_EVENT, transactionReceipt); + public static List getSettlementPeriodSetEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(SETTLEMENTPERIODSET_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { SettlementPeriodSetEventResponse typedResponse = new SettlementPeriodSetEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.settlementPeriod = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); @@ -796,75 +760,140 @@ class TangemBridgeProcessor extends Contract { return responses; } - public Flowable refundAccountSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public static SettlementPeriodSetEventResponse getSettlementPeriodSetEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(SETTLEMENTPERIODSET_EVENT, log); + SettlementPeriodSetEventResponse typedResponse = new SettlementPeriodSetEventResponse(); + typedResponse.log = log; + typedResponse.settlementPeriod = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable settlementPeriodSetEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getSettlementPeriodSetEventFromLog(log)); + } + + public Flowable settlementPeriodSetEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(REFUNDACCOUNTSET_EVENT)); - return refundAccountSetEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(SETTLEMENTPERIODSET_EVENT)); + return settlementPeriodSetEventFlowable(filter); } - public Flowable refundProcessedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getRefundProcessedEventFromLog(log)); - } - - public Flowable refundProcessedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(REFUNDPROCESSED_EVENT)); - return refundProcessedEventFlowable(filter); - } - - public static List getSettlementProcessedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(SETTLEMENTPROCESSED_EVENT, transactionReceipt); + public static List getSettlementProcessedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(SETTLEMENTPROCESSED_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { SettlementProcessedEventResponse typedResponse = new SettlementProcessedEventResponse(); typedResponse.log = eventValues.getLog(); - typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(2).getValue(); typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.fee = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); responses.add(typedResponse); } return responses; } - public Flowable roleAdminChangedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getRoleAdminChangedEventFromLog(log)); + public static SettlementProcessedEventResponse getSettlementProcessedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(SETTLEMENTPROCESSED_EVENT, log); + SettlementProcessedEventResponse typedResponse = new SettlementProcessedEventResponse(); + typedResponse.log = log; + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(2).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; } - public Flowable roleAdminChangedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public Flowable settlementProcessedEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getSettlementProcessedEventFromLog(log)); + } + + public Flowable settlementProcessedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(ROLEADMINCHANGED_EVENT)); - return roleAdminChangedEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(SETTLEMENTPROCESSED_EVENT)); + return settlementProcessedEventFlowable(filter); } - public Flowable roleGrantedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getRoleGrantedEventFromLog(log)); + public static List getUnpausedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(UNPAUSED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + UnpausedEventResponse typedResponse = new UnpausedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.account = (String) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; } - public Flowable roleGrantedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public static UnpausedEventResponse getUnpausedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(UNPAUSED_EVENT, log); + UnpausedEventResponse typedResponse = new UnpausedEventResponse(); + typedResponse.log = log; + typedResponse.account = (String) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable unpausedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getUnpausedEventFromLog(log)); + } + + public Flowable unpausedEventFlowable(DefaultBlockParameter startBlock, + DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(ROLEGRANTED_EVENT)); - return roleGrantedEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(UNPAUSED_EVENT)); + return unpausedEventFlowable(filter); } - public Flowable roleRevokedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getRoleRevokedEventFromLog(log)); + public static List getUnsettledTransactionProcessedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(UNSETTLEDTRANSACTIONPROCESSED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + UnsettledTransactionProcessedEventResponse typedResponse = new UnsettledTransactionProcessedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; } - public Flowable roleRevokedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public static UnsettledTransactionProcessedEventResponse getUnsettledTransactionProcessedEventFromLog( + Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(UNSETTLEDTRANSACTIONPROCESSED_EVENT, log); + UnsettledTransactionProcessedEventResponse typedResponse = new UnsettledTransactionProcessedEventResponse(); + typedResponse.log = log; + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable unsettledTransactionProcessedEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getUnsettledTransactionProcessedEventFromLog(log)); + } + + public Flowable unsettledTransactionProcessedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(ROLEREVOKED_EVENT)); - return roleRevokedEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(UNSETTLEDTRANSACTIONPROCESSED_EVENT)); + return unsettledTransactionProcessedEventFlowable(filter); } - public Flowable saveRefundProcessedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getSaveRefundProcessedEventFromLog(log)); - } - - public static List getVerifiedBalanceIncreasedForEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(VERIFIEDBALANCEINCREASEDFOR_EVENT, transactionReceipt); + public static List getVerifiedBalanceIncreasedForEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(VERIFIEDBALANCEINCREASEDFOR_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { VerifiedBalanceIncreasedForEventResponse typedResponse = new VerifiedBalanceIncreasedForEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); @@ -874,26 +903,33 @@ class TangemBridgeProcessor extends Contract { return responses; } - public Flowable saveRefundProcessedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public static VerifiedBalanceIncreasedForEventResponse getVerifiedBalanceIncreasedForEventFromLog( + Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(VERIFIEDBALANCEINCREASEDFOR_EVENT, log); + VerifiedBalanceIncreasedForEventResponse typedResponse = new VerifiedBalanceIncreasedForEventResponse(); + typedResponse.log = log; + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.increase = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable verifiedBalanceIncreasedForEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getVerifiedBalanceIncreasedForEventFromLog(log)); + } + + public Flowable verifiedBalanceIncreasedForEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(SAVEREFUNDPROCESSED_EVENT)); - return saveRefundProcessedEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(VERIFIEDBALANCEINCREASEDFOR_EVENT)); + return verifiedBalanceIncreasedForEventFlowable(filter); } - public Flowable securityDelaySetEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getSecurityDelaySetEventFromLog(log)); - } - - public Flowable securityDelaySetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(SECURITYDELAYSET_EVENT)); - return securityDelaySetEventFlowable(filter); - } - - public static List getVerifiedBalanceSetForEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(VERIFIEDBALANCESETFOR_EVENT, transactionReceipt); + public static List getVerifiedBalanceSetForEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(VERIFIEDBALANCESETFOR_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { VerifiedBalanceSetForEventResponse typedResponse = new VerifiedBalanceSetForEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); @@ -904,7 +940,7 @@ class TangemBridgeProcessor extends Contract { } public static VerifiedBalanceSetForEventResponse getVerifiedBalanceSetForEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(VERIFIEDBALANCESETFOR_EVENT, log); + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(VERIFIEDBALANCESETFOR_EVENT, log); VerifiedBalanceSetForEventResponse typedResponse = new VerifiedBalanceSetForEventResponse(); typedResponse.log = log; typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); @@ -912,486 +948,671 @@ class TangemBridgeProcessor extends Contract { return typedResponse; } - public Flowable verifiedBalanceSetForEventFlowable(EthFilter filter) { + public Flowable verifiedBalanceSetForEventFlowable( + EthFilter filter) { return web3j.ethLogFlowable(filter).map(log -> getVerifiedBalanceSetForEventFromLog(log)); } - public Flowable verifiedBalanceSetForEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public Flowable verifiedBalanceSetForEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); filter.addSingleTopic(EventEncoder.encode(VERIFIEDBALANCESETFOR_EVENT)); return verifiedBalanceSetForEventFlowable(filter); } - public Flowable settlementPeriodSetEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getSettlementPeriodSetEventFromLog(log)); + public static List getWithdrawalProcessedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(WITHDRAWALPROCESSED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + WithdrawalProcessedEventResponse typedResponse = new WithdrawalProcessedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; } - public Flowable settlementPeriodSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public static WithdrawalProcessedEventResponse getWithdrawalProcessedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(WITHDRAWALPROCESSED_EVENT, log); + WithdrawalProcessedEventResponse typedResponse = new WithdrawalProcessedEventResponse(); + typedResponse.log = log; + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable withdrawalProcessedEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getWithdrawalProcessedEventFromLog(log)); + } + + public Flowable withdrawalProcessedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(SETTLEMENTPERIODSET_EVENT)); - return settlementPeriodSetEventFlowable(filter); - } - - public Flowable settlementProcessedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getSettlementProcessedEventFromLog(log)); - } - - public Flowable settlementProcessedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(SETTLEMENTPROCESSED_EVENT)); - return settlementProcessedEventFlowable(filter); - } - - public Flowable unpausedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getUnpausedEventFromLog(log)); - } - - public Flowable unpausedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(UNPAUSED_EVENT)); - return unpausedEventFlowable(filter); - } - - public Flowable verifiedBalanceIncreasedForEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getVerifiedBalanceIncreasedForEventFromLog(log)); - } - - public Flowable verifiedBalanceIncreasedForEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(VERIFIEDBALANCEINCREASEDFOR_EVENT)); - return verifiedBalanceIncreasedForEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(WITHDRAWALPROCESSED_EVENT)); + return withdrawalProcessedEventFlowable(filter); } public RemoteFunctionCall AUTHORIZATION_PROCESSOR_ROLE() { final Function function = new Function(FUNC_AUTHORIZATION_PROCESSOR_ROLE, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, byte[].class); } public RemoteFunctionCall BALANCE_VERIFIER_ROLE() { final Function function = new Function(FUNC_BALANCE_VERIFIER_ROLE, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall CARD_CONFIRMER_ROLE() { + final Function function = new Function(FUNC_CARD_CONFIRMER_ROLE, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, byte[].class); } public RemoteFunctionCall DEBT_PROCESSOR_ROLE() { final Function function = new Function(FUNC_DEBT_PROCESSOR_ROLE, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, byte[].class); } public RemoteFunctionCall DEFAULT_ADMIN_ROLE() { final Function function = new Function(FUNC_DEFAULT_ADMIN_ROLE, - List.of(), - List.of(new TypeReference() { - })); - return executeRemoteCallSingleValueReturn(function, byte[].class); - } - - public RemoteFunctionCall FIXED_FEE_SETTER_ROLE() { - final Function function = new Function(FUNC_FIXED_FEE_SETTER_ROLE, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, byte[].class); } public RemoteFunctionCall PAUSER_ROLE() { final Function function = new Function(FUNC_PAUSER_ROLE, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, byte[].class); } - public RemoteFunctionCall PAYMENT_ACCOUNT_SETTER_ROLE() { - final Function function = new Function(FUNC_PAYMENT_ACCOUNT_SETTER_ROLE, - List.of(), - List.of(new TypeReference() { - })); + public RemoteFunctionCall PAYMENT_ACCOUNT_DEPLOYER_ROLE() { + final Function function = new Function(FUNC_PAYMENT_ACCOUNT_DEPLOYER_ROLE, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall PAYMENT_ACCOUNT_PROPERTY_SETTER_ROLE() { + final Function function = new Function(FUNC_PAYMENT_ACCOUNT_PROPERTY_SETTER_ROLE, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, byte[].class); } public RemoteFunctionCall PROPERTY_SETTER_ROLE() { final Function function = new Function(FUNC_PROPERTY_SETTER_ROLE, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, byte[].class); } public RemoteFunctionCall REFUND_PROCESSOR_ROLE() { final Function function = new Function(FUNC_REFUND_PROCESSOR_ROLE, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, byte[].class); } public RemoteFunctionCall SETTLEMENT_PROCESSOR_ROLE() { final Function function = new Function(FUNC_SETTLEMENT_PROCESSOR_ROLE, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, byte[].class); } - public RemoteFunctionCall fixedFee() { - final Function function = new Function(FUNC_FIXEDFEE, - List.of(), - List.of(new TypeReference() { - })); - return executeRemoteCallSingleValueReturn(function, BigInteger.class); + public RemoteFunctionCall WITHDRAWAL_PROCESSOR_ROLE() { + final Function function = new Function(FUNC_WITHDRAWAL_PROCESSOR_ROLE, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); + return executeRemoteCallSingleValueReturn(function, byte[].class); } - public RemoteFunctionCall getPaymentAccount(String card) { - final Function function = new Function(FUNC_GETPAYMENTACCOUNT, - List.of(new Address(160, card)), - List.of(new TypeReference
() { - })); - return executeRemoteCallSingleValueReturn(function, String.class); + public RemoteFunctionCall deployPaymentAccount(String owner, + String cardAddress, CardParams cardParams, BigInteger authLimitMargin_, + byte[] ownerDeployAcceptanceSignature, byte[] cardDeployAcceptanceSignature) { + final Function function = new Function( + FUNC_DEPLOYPAYMENTACCOUNT, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, owner), + new org.web3j.abi.datatypes.Address(160, cardAddress), + cardParams, + new org.web3j.abi.datatypes.generated.Uint256(authLimitMargin_), + new org.web3j.abi.datatypes.DynamicBytes(ownerDeployAcceptanceSignature), + new org.web3j.abi.datatypes.DynamicBytes(cardDeployAcceptanceSignature)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); } public RemoteFunctionCall getRoleAdmin(byte[] role) { final Function function = new Function(FUNC_GETROLEADMIN, - List.of(new Bytes32(role)), - List.of(new TypeReference() { - })); + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(role)), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, byte[].class); } public RemoteFunctionCall getRoleMember(byte[] role, BigInteger index) { final Function function = new Function(FUNC_GETROLEMEMBER, - Arrays.asList(new Bytes32(role), - new Uint256(index)), - List.of(new TypeReference
() { - })); + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(role), + new org.web3j.abi.datatypes.generated.Uint256(index)), + Arrays.>asList(new TypeReference
() {})); return executeRemoteCallSingleValueReturn(function, String.class); } public RemoteFunctionCall getRoleMemberCount(byte[] role) { final Function function = new Function(FUNC_GETROLEMEMBERCOUNT, - List.of(new Bytes32(role)), - List.of(new TypeReference() { - })); + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(role)), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } public RemoteFunctionCall grantRole(byte[] role, String account) { final Function function = new Function( FUNC_GRANTROLE, - Arrays.asList(new Bytes32(role), - new Address(160, account)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(role), + new org.web3j.abi.datatypes.Address(160, account)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } public RemoteFunctionCall hasRole(byte[] role, String account) { final Function function = new Function(FUNC_HASROLE, - Arrays.asList(new Bytes32(role), - new Address(160, account)), - List.of(new TypeReference() { - })); + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(role), + new org.web3j.abi.datatypes.Address(160, account)), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, Boolean.class); } - public RemoteFunctionCall increaseVerifiedBalanceFor(String paymentAccount, BigInteger increase) { + public RemoteFunctionCall increaseVerifiedBalanceFor(String paymentAccount, + BigInteger increase) { final Function function = new Function( FUNC_INCREASEVERIFIEDBALANCEFOR, - Arrays.asList(new Address(160, paymentAccount), - new Uint256(increase)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentAccount), + new org.web3j.abi.datatypes.generated.Uint256(increase)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } + public RemoteFunctionCall isCardConfirmer(String account) { + final Function function = new Function(FUNC_ISCARDCONFIRMER, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, account)), + Arrays.>asList(new TypeReference() {})); + return executeRemoteCallSingleValueReturn(function, Boolean.class); + } + public RemoteFunctionCall pause() { final Function function = new Function( FUNC_PAUSE, - List.of(), - Collections.emptyList()); + Arrays.asList(), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } public RemoteFunctionCall paused() { final Function function = new Function(FUNC_PAUSED, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, Boolean.class); } public RemoteFunctionCall paymentAccountFactory() { final Function function = new Function(FUNC_PAYMENTACCOUNTFACTORY, - List.of(), - List.of(new TypeReference
() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference
() {})); return executeRemoteCallSingleValueReturn(function, String.class); } public RemoteFunctionCall paymentReceiver() { final Function function = new Function(FUNC_PAYMENTRECEIVER, - List.of(), - List.of(new TypeReference
() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference
() {})); return executeRemoteCallSingleValueReturn(function, String.class); } public RemoteFunctionCall paymentToken() { final Function function = new Function(FUNC_PAYMENTTOKEN, - List.of(), - List.of(new TypeReference
() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference
() {})); return executeRemoteCallSingleValueReturn(function, String.class); } - public RemoteFunctionCall processAuthorization(String card, BigInteger transactionId, BigInteger amount, byte[] otp, BigInteger otpCounter, Boolean forced) { - final Function function = new Function( - FUNC_PROCESSAUTHORIZATION, - Arrays.asList(new Address(160, card), - new Uint256(transactionId), - new Uint256(amount), - new org.web3j.abi.datatypes.generated.Bytes16(otp), - new org.web3j.abi.datatypes.generated.Uint16(otpCounter), - new Bool(forced)), - Collections.emptyList()); - return executeRemoteCallTransaction(function); - } - - public RemoteFunctionCall processAuthorizationChange(String card, BigInteger transactionId, BigInteger amount) { + public RemoteFunctionCall processAuthorizationChange(String paymentAccount, + String card, BigInteger transactionId, BigInteger amount) { final Function function = new Function( FUNC_PROCESSAUTHORIZATIONCHANGE, - Arrays.asList(new Address(160, card), - new Uint256(transactionId), - new Uint256(amount)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentAccount), + new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(transactionId), + new org.web3j.abi.datatypes.generated.Uint256(amount)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall processAuthorizationNoOtp(String card, BigInteger transactionId, BigInteger amount, Boolean forced) { + public RemoteFunctionCall processDebt(String paymentAccount, String card, + BigInteger amount) { final Function function = new Function( - FUNC_PROCESSAUTHORIZATIONNOOTP, - Arrays.asList(new Address(160, card), - new Uint256(transactionId), - new Uint256(amount), - new Bool(forced)), - Collections.emptyList()); + FUNC_PROCESSDEBT, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentAccount), + new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(amount)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall processPendingRefund(String card, BigInteger transactionId) { + public RemoteFunctionCall processNoConfirmationAuthorization( + String paymentAccount, String card, BigInteger transactionId, BigInteger amount, + Boolean forced) { final Function function = new Function( - FUNC_PROCESSPENDINGREFUND, - Arrays.asList(new Address(160, card), - new Uint256(transactionId)), - Collections.emptyList()); + FUNC_PROCESSNOCONFIRMATIONAUTHORIZATION, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentAccount), + new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(transactionId), + new org.web3j.abi.datatypes.generated.Uint256(amount), + new org.web3j.abi.datatypes.Bool(forced)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall processRefund(String card, BigInteger transactionId, BigInteger refundAmount) { + public RemoteFunctionCall processOtpAuthorization(String paymentAccount, + String card, BigInteger transactionId, BigInteger amount, byte[] otp, + BigInteger otpCounter, Boolean forced) { + final Function function = new Function( + FUNC_PROCESSOTPAUTHORIZATION, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentAccount), + new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(transactionId), + new org.web3j.abi.datatypes.generated.Uint256(amount), + new org.web3j.abi.datatypes.generated.Bytes16(otp), + new org.web3j.abi.datatypes.generated.Uint16(otpCounter), + new org.web3j.abi.datatypes.Bool(forced)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processRefund(String paymentAccount, String card, + BigInteger transactionId, BigInteger refundAmount) { final Function function = new Function( FUNC_PROCESSREFUND, - Arrays.asList(new Address(160, card), - new Uint256(transactionId), - new Uint256(refundAmount)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentAccount), + new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(transactionId), + new org.web3j.abi.datatypes.generated.Uint256(refundAmount)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } public RemoteFunctionCall processRefundCallback() { final Function function = new Function( FUNC_PROCESSREFUNDCALLBACK, - List.of(), - Collections.emptyList()); + Arrays.asList(), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall processSettlement(String card, BigInteger transactionId, BigInteger amount) { + public RemoteFunctionCall processSettlement(String paymentAccount, + String card, BigInteger transactionId, BigInteger amount) { final Function function = new Function( FUNC_PROCESSSETTLEMENT, - Arrays.asList(new Address(160, card), - new Uint256(transactionId), - new Uint256(amount)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentAccount), + new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(transactionId), + new org.web3j.abi.datatypes.generated.Uint256(amount)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processSignatureAuthorization( + String paymentAccount, String card, BigInteger transactionId, BigInteger amount, + byte[] signature, Boolean forced) { + final Function function = new Function( + FUNC_PROCESSSIGNATUREAUTHORIZATION, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentAccount), + new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(transactionId), + new org.web3j.abi.datatypes.generated.Uint256(amount), + new org.web3j.abi.datatypes.DynamicBytes(signature), + new org.web3j.abi.datatypes.Bool(forced)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processUnsettledTransaction(String paymentAccount, + String card, BigInteger transactionId) { + final Function function = new Function( + FUNC_PROCESSUNSETTLEDTRANSACTION, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentAccount), + new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(transactionId)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processWithdrawal(String paymentAccount) { + final Function function = new Function( + FUNC_PROCESSWITHDRAWAL, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentAccount)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } public RemoteFunctionCall refundAccount() { final Function function = new Function(FUNC_REFUNDACCOUNT, - List.of(), - List.of(new TypeReference
() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference
() {})); return executeRemoteCallSingleValueReturn(function, String.class); } - public RemoteFunctionCall renounceRole(byte[] role, String callerConfirmation) { + public RemoteFunctionCall renounceRole(byte[] role, + String callerConfirmation) { final Function function = new Function( FUNC_RENOUNCEROLE, - Arrays.asList(new Bytes32(role), - new Address(160, callerConfirmation)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(role), + new org.web3j.abi.datatypes.Address(160, callerConfirmation)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } public RemoteFunctionCall revokeRole(byte[] role, String account) { final Function function = new Function( FUNC_REVOKEROLE, - Arrays.asList(new Bytes32(role), - new Address(160, account)), - Collections.emptyList()); - return executeRemoteCallTransaction(function); - } - - public RemoteFunctionCall savePendingRefund(String card, BigInteger transactionId, BigInteger amount) { - final Function function = new Function( - FUNC_SAVEPENDINGREFUND, - Arrays.asList(new Address(160, card), - new Uint256(transactionId), - new Uint256(amount)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(role), + new org.web3j.abi.datatypes.Address(160, account)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } public RemoteFunctionCall securityDelay() { final Function function = new Function(FUNC_SECURITYDELAY, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } - public RemoteFunctionCall setFixedFee(BigInteger fixedFee_) { + public RemoteFunctionCall setAuthLimitMarginFor(String paymentAccount, + BigInteger authLimitMargin) { final Function function = new Function( - FUNC_SETFIXEDFEE, - List.of(new Uint256(fixedFee_)), - Collections.emptyList()); - return executeRemoteCallTransaction(function); - } - - public RemoteFunctionCall setPaymentAccount(String card, String paymentAccount) { - final Function function = new Function( - FUNC_SETPAYMENTACCOUNT, - Arrays.asList(new Address(160, card), - new Address(160, paymentAccount)), - Collections.emptyList()); + FUNC_SETAUTHLIMITMARGINFOR, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentAccount), + new org.web3j.abi.datatypes.generated.Uint256(authLimitMargin)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } public RemoteFunctionCall setPaymentReceiver(String paymentReceiver_) { final Function function = new Function( FUNC_SETPAYMENTRECEIVER, - List.of(new Address(160, paymentReceiver_)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentReceiver_)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } public RemoteFunctionCall setRefundAccount(String refundAccount_) { final Function function = new Function( FUNC_SETREFUNDACCOUNT, - List.of(new Address(160, refundAccount_)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, refundAccount_)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } public RemoteFunctionCall setSecurityDelay(BigInteger securityDelay_) { final Function function = new Function( FUNC_SETSECURITYDELAY, - List.of(new Uint256(securityDelay_)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.generated.Uint256(securityDelay_)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall setSettlementPeriod(BigInteger settlementPeriod_) { - final Function function = new Function( - FUNC_SETSETTLEMENTPERIOD, - List.of(new Uint256(settlementPeriod_)), - Collections.emptyList()); - return executeRemoteCallTransaction(function); - } - - public RemoteFunctionCall setVerifiedBalanceFor(String paymentAccount, BigInteger verifiedBalance) { + public RemoteFunctionCall setVerifiedBalanceFor(String paymentAccount, + BigInteger verifiedBalance) { final Function function = new Function( FUNC_SETVERIFIEDBALANCEFOR, - Arrays.asList(new Address(160, paymentAccount), - new Uint256(verifiedBalance)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentAccount), + new org.web3j.abi.datatypes.generated.Uint256(verifiedBalance)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall settlementPeriod() { - final Function function = new Function(FUNC_SETTLEMENTPERIOD, - List.of(), - List.of(new TypeReference() { - })); - return executeRemoteCallSingleValueReturn(function, BigInteger.class); - } - public RemoteFunctionCall supportsInterface(byte[] interfaceId) { final Function function = new Function(FUNC_SUPPORTSINTERFACE, - List.of(new Bytes4(interfaceId)), - List.of(new TypeReference() { - })); + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes4(interfaceId)), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, Boolean.class); } public RemoteFunctionCall unpause() { final Function function = new Function( FUNC_UNPAUSE, - List.of(), - Collections.emptyList()); + Arrays.asList(), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall writeOffDebt(String card, BigInteger amount) { + public RemoteFunctionCall writeOffDebt(String paymentAccount, String card, + BigInteger amount) { final Function function = new Function( FUNC_WRITEOFFDEBT, - Arrays.asList(new Address(160, card), - new Uint256(amount)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentAccount), + new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(amount)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } + @Deprecated + public static TangemBridgeProcessor load(String contractAddress, Web3j web3j, + Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + return new TangemBridgeProcessor(contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + @Deprecated + public static TangemBridgeProcessor load(String contractAddress, Web3j web3j, + TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + return new TangemBridgeProcessor(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + public static TangemBridgeProcessor load(String contractAddress, Web3j web3j, + Credentials credentials, ContractGasProvider contractGasProvider) { + return new TangemBridgeProcessor(contractAddress, web3j, credentials, contractGasProvider); + } + + public static TangemBridgeProcessor load(String contractAddress, Web3j web3j, + TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + return new TangemBridgeProcessor(contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static RemoteCall deploy(Web3j web3j, Credentials credentials, + ContractGasProvider contractGasProvider, String paymentReceiver_, String refundAccount_, + String paymentAccountFactory_, String paymentToken_, BigInteger securityDelay_) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentReceiver_), + new org.web3j.abi.datatypes.Address(160, refundAccount_), + new org.web3j.abi.datatypes.Address(160, paymentAccountFactory_), + new org.web3j.abi.datatypes.Address(160, paymentToken_), + new org.web3j.abi.datatypes.generated.Uint256(securityDelay_))); + return deployRemoteCall(TangemBridgeProcessor.class, web3j, credentials, contractGasProvider, getDeploymentBinary(), encodedConstructor); + } + + public static RemoteCall deploy(Web3j web3j, + TransactionManager transactionManager, ContractGasProvider contractGasProvider, + String paymentReceiver_, String refundAccount_, String paymentAccountFactory_, + String paymentToken_, BigInteger securityDelay_) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentReceiver_), + new org.web3j.abi.datatypes.Address(160, refundAccount_), + new org.web3j.abi.datatypes.Address(160, paymentAccountFactory_), + new org.web3j.abi.datatypes.Address(160, paymentToken_), + new org.web3j.abi.datatypes.generated.Uint256(securityDelay_))); + return deployRemoteCall(TangemBridgeProcessor.class, web3j, transactionManager, contractGasProvider, getDeploymentBinary(), encodedConstructor); + } + + @Deprecated + public static RemoteCall deploy(Web3j web3j, Credentials credentials, + BigInteger gasPrice, BigInteger gasLimit, String paymentReceiver_, + String refundAccount_, String paymentAccountFactory_, String paymentToken_, + BigInteger securityDelay_) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentReceiver_), + new org.web3j.abi.datatypes.Address(160, refundAccount_), + new org.web3j.abi.datatypes.Address(160, paymentAccountFactory_), + new org.web3j.abi.datatypes.Address(160, paymentToken_), + new org.web3j.abi.datatypes.generated.Uint256(securityDelay_))); + return deployRemoteCall(TangemBridgeProcessor.class, web3j, credentials, gasPrice, gasLimit, getDeploymentBinary(), encodedConstructor); + } + + @Deprecated + public static RemoteCall deploy(Web3j web3j, + TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit, + String paymentReceiver_, String refundAccount_, String paymentAccountFactory_, + String paymentToken_, BigInteger securityDelay_) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Address(160, paymentReceiver_), + new org.web3j.abi.datatypes.Address(160, refundAccount_), + new org.web3j.abi.datatypes.Address(160, paymentAccountFactory_), + new org.web3j.abi.datatypes.Address(160, paymentToken_), + new org.web3j.abi.datatypes.generated.Uint256(securityDelay_))); + return deployRemoteCall(TangemBridgeProcessor.class, web3j, transactionManager, gasPrice, gasLimit, getDeploymentBinary(), encodedConstructor); + } + + public static void linkLibraries(List references) { + librariesLinkedBinary = linkBinaryWithReferences(BINARY, references); + } + + private static String getDeploymentBinary() { + if (librariesLinkedBinary != null) { + return librariesLinkedBinary; + } else { + return BINARY; + } + } + + public static class OtpState extends StaticStruct { + public byte[] otp; + + public BigInteger counter; + + public OtpState(byte[] otp, BigInteger counter) { + super(new org.web3j.abi.datatypes.generated.Bytes16(otp), + new org.web3j.abi.datatypes.generated.Uint16(counter)); + this.otp = otp; + this.counter = counter; + } + + public OtpState(Bytes16 otp, Uint16 counter) { + super(otp, counter); + this.otp = otp.getValue(); + this.counter = counter.getValue(); + } + } + + public static class LimitsParams extends StaticStruct { + public BigInteger singleTransactionLimit; + + public BigInteger spendLimit; + + public BigInteger noConfirmationSpendLimit; + + public BigInteger spendLimitsPeriod; + + public LimitsParams(BigInteger singleTransactionLimit, BigInteger spendLimit, + BigInteger noConfirmationSpendLimit, BigInteger spendLimitsPeriod) { + super(new org.web3j.abi.datatypes.generated.Uint256(singleTransactionLimit), + new org.web3j.abi.datatypes.generated.Uint256(spendLimit), + new org.web3j.abi.datatypes.generated.Uint256(noConfirmationSpendLimit), + new org.web3j.abi.datatypes.generated.Uint256(spendLimitsPeriod)); + this.singleTransactionLimit = singleTransactionLimit; + this.spendLimit = spendLimit; + this.noConfirmationSpendLimit = noConfirmationSpendLimit; + this.spendLimitsPeriod = spendLimitsPeriod; + } + + public LimitsParams(Uint256 singleTransactionLimit, Uint256 spendLimit, + Uint256 noConfirmationSpendLimit, Uint256 spendLimitsPeriod) { + super(singleTransactionLimit, spendLimit, noConfirmationSpendLimit, spendLimitsPeriod); + this.singleTransactionLimit = singleTransactionLimit.getValue(); + this.spendLimit = spendLimit.getValue(); + this.noConfirmationSpendLimit = noConfirmationSpendLimit.getValue(); + this.spendLimitsPeriod = spendLimitsPeriod.getValue(); + } + } + + public static class CardParams extends StaticStruct { + public Boolean isOwner; + + public OtpState otpState; + + public LimitsParams limitsParams; + + public CardParams(Boolean isOwner, OtpState otpState, LimitsParams limitsParams) { + super(new org.web3j.abi.datatypes.Bool(isOwner), + otpState, + limitsParams); + this.isOwner = isOwner; + this.otpState = otpState; + this.limitsParams = limitsParams; + } + + public CardParams(Bool isOwner, OtpState otpState, LimitsParams limitsParams) { + super(isOwner, otpState, limitsParams); + this.isOwner = isOwner.getValue(); + this.otpState = otpState; + this.limitsParams = limitsParams; + } + } + + public static class AuthLimitMarginSetForEventResponse extends BaseEventResponse { + public String paymentAccount; + + public BigInteger authLimitMargin; + } + public static class AuthorizationChangeProcessedEventResponse extends BaseEventResponse { + public String paymentAccount; + public String card; public BigInteger transactionId; public BigInteger amount; - - public BigInteger fee; } public static class AuthorizationProcessedEventResponse extends BaseEventResponse { + public String paymentAccount; + public String card; public BigInteger transactionId; public BigInteger amount; - - public BigInteger fee; } - public static class DebtWriteOffProcessedEventResponse extends BaseEventResponse { + public static class DebtProcessedEventResponse extends BaseEventResponse { + public String paymentAccount; + public String card; public BigInteger amount; } - public static class FixedFeeSetEventResponse extends BaseEventResponse { - public BigInteger fixedFee; + public static class DebtWriteOffProcessedEventResponse extends BaseEventResponse { + public String paymentAccount; + + public String card; + + public BigInteger amount; } public static class PausedEventResponse extends BaseEventResponse { public String account; } - public static class PaymentAccountSetEventResponse extends BaseEventResponse { - public String card; + public static class PaymentAccountDeployedEventResponse extends BaseEventResponse { + public String owner; + + public String cardAddress; public String paymentAccount; } @@ -1400,19 +1621,13 @@ class TangemBridgeProcessor extends Contract { public String paymentReceiver; } - public static class PendingRefundProcessedEventResponse extends BaseEventResponse { - public String card; - - public BigInteger transactionId; - - public BigInteger amount; - } - public static class RefundAccountSetEventResponse extends BaseEventResponse { public String refundAccount; } public static class RefundProcessedEventResponse extends BaseEventResponse { + public String paymentAccount; + public String card; public BigInteger transactionId; @@ -1444,14 +1659,6 @@ class TangemBridgeProcessor extends Contract { public String sender; } - public static class SaveRefundProcessedEventResponse extends BaseEventResponse { - public String card; - - public BigInteger transactionId; - - public BigInteger amount; - } - public static class SecurityDelaySetEventResponse extends BaseEventResponse { public BigInteger securityDelay; } @@ -1461,19 +1668,27 @@ class TangemBridgeProcessor extends Contract { } public static class SettlementProcessedEventResponse extends BaseEventResponse { + public String paymentAccount; + public String card; public BigInteger transactionId; public BigInteger amount; - - public BigInteger fee; } public static class UnpausedEventResponse extends BaseEventResponse { public String account; } + public static class UnsettledTransactionProcessedEventResponse extends BaseEventResponse { + public String paymentAccount; + + public String card; + + public BigInteger transactionId; + } + public static class VerifiedBalanceIncreasedForEventResponse extends BaseEventResponse { public String paymentAccount; @@ -1485,4 +1700,8 @@ class TangemBridgeProcessor extends Contract { public BigInteger verifiedBalance; } -} + + public static class WithdrawalProcessedEventResponse extends BaseEventResponse { + public String paymentAccount; + } +} \ No newline at end of file diff --git a/libs/visa/src/main/java/com/tangem/lib/visa/TangemPaymentAccount.java b/libs/visa/src/main/java/com/tangem/lib/visa/TangemPaymentAccount.java index 834d85f3c2..df19c6cee9 100644 --- a/libs/visa/src/main/java/com/tangem/lib/visa/TangemPaymentAccount.java +++ b/libs/visa/src/main/java/com/tangem/lib/visa/TangemPaymentAccount.java @@ -1,10 +1,30 @@ package com.tangem.lib.visa; +import io.reactivex.Flowable; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; import org.web3j.abi.EventEncoder; import org.web3j.abi.FunctionEncoder; import org.web3j.abi.TypeReference; -import org.web3j.abi.datatypes.*; -import org.web3j.abi.datatypes.generated.*; +import org.web3j.abi.datatypes.Address; +import org.web3j.abi.datatypes.Bool; +import org.web3j.abi.datatypes.DynamicArray; +import org.web3j.abi.datatypes.DynamicBytes; +import org.web3j.abi.datatypes.Event; +import org.web3j.abi.datatypes.Function; +import org.web3j.abi.datatypes.StaticStruct; +import org.web3j.abi.datatypes.Type; +import org.web3j.abi.datatypes.Utf8String; +import org.web3j.abi.datatypes.generated.Bytes1; +import org.web3j.abi.datatypes.generated.Bytes16; +import org.web3j.abi.datatypes.generated.Bytes32; +import org.web3j.abi.datatypes.generated.Uint16; +import org.web3j.abi.datatypes.generated.Uint256; +import org.web3j.abi.datatypes.generated.Uint64; import org.web3j.crypto.Credentials; import org.web3j.protocol.Web3j; import org.web3j.protocol.core.DefaultBlockParameter; @@ -14,36 +34,36 @@ import org.web3j.protocol.core.methods.request.EthFilter; import org.web3j.protocol.core.methods.response.BaseEventResponse; import org.web3j.protocol.core.methods.response.Log; import org.web3j.protocol.core.methods.response.TransactionReceipt; -import org.web3j.tuples.generated.Tuple3; +import org.web3j.tuples.generated.Tuple4; +import org.web3j.tuples.generated.Tuple5; import org.web3j.tuples.generated.Tuple7; import org.web3j.tx.Contract; import org.web3j.tx.TransactionManager; import org.web3j.tx.gas.ContractGasProvider; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.Callable; - -import io.reactivex.Flowable; - /** *

Auto generated code. *

Do not modify! *

Please use the web3j command line tools, - * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the - * codegen module to update. + * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the + * codegen module to update. * - *

Generated with web3j version 1.5.2. + *

Generated with web3j version 1.6.1. */ @SuppressWarnings("rawtypes") -class TangemPaymentAccount extends Contract { - public static final String BINARY = "60c06040523060a0523480156200001557600080fd5b5060405162005aa938038062005aa983398101604081905262000038916200010a565b6001600160a01b0381166080526200004f62000056565b506200013c565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff1615620000a75760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b0390811614620001075780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b6000602082840312156200011d57600080fd5b81516001600160a01b03811681146200013557600080fd5b9392505050565b60805160a05161592b6200017e600039600081816132fe0152818161332701526135880152600081816104d301528181610578015261290d015261592b6000f3fe6080604052600436106102c85760003560e01c806386489ba911610175578063c470d703116100dc578063df7727b411610095578063e3dbffd51161006f578063e3dbffd514610a40578063e4d7ddb914610a60578063e6e268f414610a80578063f89db45714610a9557600080fd5b8063df7727b4146102f5578063e1a8eafd146109b2578063e37b8154146109d257600080fd5b8063c470d703146108da578063c81656c81461090e578063ce1b1d431461093b578063d47ae89c1461095b578063dbed8faa14610970578063dcab7e501461099057600080fd5b8063a789457d1161012e578063a789457d1461077b578063ad3cb1cc14610827578063b4d02d0d14610865578063b9603bdf14610885578063be69191d146108a5578063c45a0155146108ba57600080fd5b806386489ba9146106c657806386f15d09146106e65780638bbe11af146106fb5780638c3b2a9b1461071b5780638da5cb5b1461073b578063a075b7c71461075b57600080fd5b80633c1a5012116102345780636b2a3830116101ed5780637da0a877116101c75780637da0a87714610569578063806679e41461059c57806384b0196e146105bc578063860aefcf146105e457600080fd5b80636b2a3830146105135780636b4943a7146105335780637b1039991461054957600080fd5b80633c1a501214610439578063456575e51461045957806346236eb9146104795780634f1ef2861461048e57806352d1902d146104a1578063572b6c05146104b657600080fd5b806322611280116102865780632261128014610377578063229865d11461038c5780633013ce29146103ac578063309433ba146103e457806334284dfb1461040457806335ba9af81461042457600080fd5b806274f356146102cd57806301e948ff146102f55780630381b4dd1461030a5780630f1071be1461032c57806319cdeb6f146103415780631f852f4b14610357575b600080fd5b3480156102d957600080fd5b506102e2610ab5565b6040519081526020015b60405180910390f35b34801561030157600080fd5b506102e2610acf565b34801561031657600080fd5b5061032a610325366004614b0d565b610ae0565b005b34801561033857600080fd5b506102e2610dde565b34801561034d57600080fd5b506102e260065481565b34801561036357600080fd5b5061032a610372366004614b8d565b610e63565b34801561038357600080fd5b5061032a610fa7565b34801561039857600080fd5b5061032a6103a7366004614be2565b611034565b3480156103b857600080fd5b506000546103cc906001600160a01b031681565b6040516001600160a01b0390911681526020016102ec565b3480156103f057600080fd5b5061032a6103ff366004614c2a565b6110cb565b34801561041057600080fd5b5061032a61041f366004614cb0565b611337565b34801561043057600080fd5b506102e26113fd565b34801561044557600080fd5b5061032a610454366004614d0b565b611490565b34801561046557600080fd5b5061032a610474366004614b0d565b61151c565b34801561048557600080fd5b50601c546102e2565b61032a61049c366004614d3e565b6115bc565b3480156104ad57600080fd5b506102e26115db565b3480156104c257600080fd5b506105036104d1366004614d0b565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0390811691161490565b60405190151581526020016102ec565b34801561051f57600080fd5b5061032a61052e366004614be2565b6115f8565b34801561053f57600080fd5b506102e260075481565b34801561055557600080fd5b506002546103cc906001600160a01b031681565b34801561057557600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006103cc565b3480156105a857600080fd5b5061032a6105b7366004614e02565b611749565b3480156105c857600080fd5b506105d16117ed565b6040516102ec9796959493929190614e8b565b3480156105f057600080fd5b506040805160a08082018352600d54825282518084018452600e548152600f546020828101919091528084019190915283518085018552601054815260115481830152838501528351808201855260125481526060808501919091526013546080808601919091528551938401865260145484528551808701875260155481526016548185015284840152855180870187526017548152601854818501528487015285519283019095526019548252820152601a5492810192909252601b546106b7929083565b6040516102ec93929190614f6c565b3480156106d257600080fd5b5061032a6106e1366004614f97565b611899565b3480156106f257600080fd5b50610503611a66565b34801561070757600080fd5b5061032a610716366004614be2565b611a90565b34801561072757600080fd5b5061032a610736366004614ff0565b611ba9565b34801561074757600080fd5b506004546103cc906001600160a01b031681565b34801561076757600080fd5b5061032a610776366004615023565b611cf8565b34801561078757600080fd5b506040805180820182526008546001600160a01b039081168252825180840184526009546001600160801b0319608082811b8216845261ffff600160801b9384900481166020868101919091528088019590955287518089018952600a5490961686528751808901909852600b549182901b9092168752919091041684820152810192909252600c54610818929083565b6040516102ec93929190615086565b34801561083357600080fd5b50610858604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516102ec91906150af565b34801561087157600080fd5b5061032a610880366004614be2565b611de0565b34801561089157600080fd5b5061032a6108a0366004614be2565b611ee3565b3480156108b157600080fd5b5061050361201c565b3480156108c657600080fd5b506001546103cc906001600160a01b031681565b3480156108e657600080fd5b507f3fc394ec90f88dd7e18c46ffeef55f1b6719a2027f4ae9fbc4ab118271891200546102e2565b34801561091a57600080fd5b506102e2610929366004614be2565b6000908152601d602052604090205490565b34801561094757600080fd5b506003546103cc906001600160a01b031681565b34801561096757600080fd5b506102e2612044565b34801561097c57600080fd5b5061032a61098b366004614be2565b6120c8565b34801561099c57600080fd5b506000805160206154fc833981519152546102e2565b3480156109be57600080fd5b5061032a6109cd366004614d0b565b612127565b3480156109de57600080fd5b50610a1b6109ed366004614be2565b600560205260009081526040902080546001909101546001600160801b03811690600160801b900460ff1683565b604080519384526001600160801b0390921660208401521515908201526060016102ec565b348015610a4c57600080fd5b5061032a610a5b366004614be2565b6122c6565b348015610a6c57600080fd5b5061032a610a7b366004614b0d565b6124ac565b348015610a8c57600080fd5b506102e261265c565b348015610aa157600080fd5b5061032a610ab0366004614be2565b612673565b6000806000805160206154dc8339815191525b5492915050565b6000610adb600061284d565b905090565b6003546001600160a01b0316610af4612909565b6001600160a01b0316146040518060600160405280603381526020016153166033913990610b3e5760405162461bcd60e51b8152600401610b3591906150af565b60405180910390fd5b50610b488161295b565b60008281526005602052604081205490818311610b7b575081808203828214610b7557610b75858261299b565b50610bff565b50806000610b89600d6129e0565b8054909150600081851015610bbf57610bae858303610ba98560006129ff565b612a6b565b9050610bbc81610ba9610acf565b90505b8015610be7576000610bd382878903612a6b565b948501949050610be584826000612a83565b505b838603868514610bfa57610bfa81612aaa565b505050505b8160075410156040518060600160405280602981526020016158226029913990610c3c5760405162461bcd60e51b8152600401610b3591906150af565b50600780548390039055600084815260056020908152604080832092835560019290920180546001600160881b0319169055600354825163659bf9d960e11b81529251610cdf936001600160a01b039092169263cb37f3b292600480820193918290030181865afa158015610cb5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cd991906150c2565b82612b0b565b604080518481526020810183905285917fa06caae8c696e5deb33f685024044b74125cb932e11521aef47de748321657c4910160405180910390a26000546040516370a0823160e01b81523060048201527f273f30c859762d267fd40559825ed2fa0f467908c92843dfd0184b9d577dc082916001600160a01b0316906370a0823190602401602060405180830381865afa158015610d82573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610da691906150df565b600754610db1610ab5565b601c546040805194855260208501939093529183015260608201526080015b60405180910390a150505050565b6000610adb600360009054906101000a90046001600160a01b03166001600160a01b0316630f1071be6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e36573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e5a91906150df565b6283d600612a6b565b6004546001600160a01b0316610e77612909565b6001600160a01b03161480610eae5750610e916008612c1b565b546001600160a01b0316610ea3612909565b6001600160a01b0316145b6040518060600160405280602f815260200161589e602f913990610ee55760405162461bcd60e51b8152600401610b3591906150af565b50610ef1838383612c36565b600480546001600160a01b038581166001600160a01b0319831681178455600254604051635cd2673760e01b815293831694840185905260248401919091521690635cd2673790604401600060405180830381600087803b158015610f5557600080fd5b505af1158015610f69573d6000803e3d6000fd5b50506040516001600160a01b03871681527f50146d0e3c60aa1d17a70635b05494f864e86144a2201275021014fbf08bafe292506020019050610dd0565b6004546001600160a01b0316610fbb612909565b6001600160a01b03161480610ff25750610fd56008612c1b565b546001600160a01b0316610fe7612909565b6001600160a01b0316145b6040518060600160405280602f815260200161589e602f9139906110295760405162461bcd60e51b8152600401610b3591906150af565b50611032612d2a565b565b6004546001600160a01b0316611048612909565b6001600160a01b0316148061107f57506110626008612c1b565b546001600160a01b0316611074612909565b6001600160a01b0316145b6040518060600160405280602f815260200161589e602f9139906110b65760405162461bcd60e51b8152600401610b3591906150af565b506110c8816110c3612044565b612d7b565b50565b6004546001600160a01b03166110df612909565b6001600160a01b0316148061111657506110f96008612c1b565b546001600160a01b031661110b612909565b6001600160a01b0316145b6040518060600160405280602f815260200161589e602f91399061114d5760405162461bcd60e51b8152600401610b3591906150af565b50611159858383612c36565b600c541561122857600a546040805180820182526001600160a01b03888116825282518084019093526001600160801b03198816835261ffff87166020848101919091528201929092529116906111bb906111b2612044565b60089190612e79565b600254604051630747fc3760e41b81526001600160a01b03838116600483015288811660248301529091169063747fc37090604401600060405180830381600087803b15801561120a57600080fd5b505af115801561121e573d6000803e3d6000fd5b50505050506112db565b61127b6040518060400160405280876001600160a01b031681526020016040518060400160405280886001600160801b03191681526020018761ffff16815250815250600861302090919063ffffffff16565b60025460405163346b888d60e11b81526001600160a01b038781166004830152909116906368d7111a90602401600060405180830381600087803b1580156112c257600080fd5b505af11580156112d6573d6000803e3d6000fd5b505050505b604080516001600160a01b03871681526001600160801b03198616602082015261ffff85168183015290517fae81d31085749debb22edb9a09f263e50cf6047cfc57ec741c07ce62b1121b9a9181900360600190a15050505050565b6003546001600160a01b031661134b612909565b6001600160a01b031614604051806060016040528060338152602001615316603391399061138c5760405162461bcd60e51b8152600401610b3591906150af565b50600161139a8484846130c1565b6113a68686848461311d565b604080518681526001600160801b03198616602082015261ffff851681830152905187917faab1db26b8ce0ed14d67fd3d8eab92617f4caf4134148122ad6f2a4e20068df7919081900360600190a2505050505050565b600080546040516370a0823160e01b815230600482015282916001600160a01b0316906370a0823190602401602060405180830381865afa158015611446573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061146a91906150df565b905060075481101561147d57600061148a565b60075461148a908261510e565b91505090565b6004546001600160a01b03166114a4612909565b6001600160a01b031614806114db57506114be6008612c1b565b546001600160a01b03166114d0612909565b6001600160a01b0316145b6040518060600160405280602f815260200161589e602f9139906115125760405162461bcd60e51b8152600401610b3591906150af565b506110c881613186565b6003546001600160a01b0316611530612909565b6001600160a01b03161460405180606001604052806033815260200161531660339139906115715760405162461bcd60e51b8152600401610b3591906150af565b5061157e601c8383613274565b817fea92e836c1b642a333bb58b44d5f13c5a066c5b0f35b8d6da5032af605c9e031826040516115b091815260200190565b60405180910390a25050565b6115c46132f3565b6115cd82613398565b6115d782826134c0565b5050565b60006115e561357d565b506000805160206155cf83398151915290565b6003546001600160a01b031661160c612909565b6001600160a01b031614604051806060016040528060338152602001615316603391399061164d5760405162461bcd60e51b8152600401610b3591906150af565b5060008160065461165e9190615121565b6000546040516370a0823160e01b81523060048201529192506001600160a01b0316906370a0823190602401602060405180830381865afa1580156116a7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116cb91906150df565b8111156040518060600160405280603781526020016153c760379139906117055760405162461bcd60e51b8152600401610b3591906150af565b50600681905560408051838152602081018390527fd6004694c2f543b69302481de26731f63c1d41062795b5366d8ca20fd3e3d6d391015b60405180910390a15050565b6003546001600160a01b031661175d612909565b6001600160a01b031614604051806060016040528060338152602001615316603391399061179e5760405162461bcd60e51b8152600401610b3591906150af565b5060006117ad8484848461311d565b837f5e1266400272ee17509f7d7edb190c0fa83572f90677ee6a3bbfac22bf1f679d846040516117df91815260200190565b60405180910390a250505050565b60006060808280808381600080516020615541833981519152805490915015801561181a57506001810154155b61185e5760405162461bcd60e51b81526020600482015260156024820152741152540dcc4c8e88155b9a5b9a5d1a585b1a5e9959605a1b6044820152606401610b35565b6118666135c6565b61186e613689565b60408051600080825260208201909252600f60f81b9c939b5091995046985030975095509350915050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff166000811580156118df5750825b905060008267ffffffffffffffff1660011480156118fc5750303b155b90508115801561190a575080155b156119285760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561195257845460ff60401b1916600160401b1785555b61195a6136c8565b6119626136c8565b6119b16040518060400160405280601481526020017315185b99d95b54185e5b595b9d1058d8dbdd5b9d60621b815250604051806040016040528060018152602001603160f81b8152506136d0565b6119b96136c8565b6119c1612909565b600180546001600160a01b03199081166001600160a01b03938416179091556004805482168e841617905560028054909116918c16919091179055611a13611a0b8a8a8a8a6136e2565b600d90613780565b8315611a5957845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050505050565b6000805160206154fc8339815191525460009060008051602061549c83398151915290151561148a565b6000818152600560205260409020600181015490546001600160801b0390911690611ab9610dde565b611ac39083615121565b42116040518060600160405280602b8152602001615349602b913990611afc5760405162461bcd60e51b8152600401610b3591906150af565b508060075410156040518060600160405280602981526020016158226029913990611b3a5760405162461bcd60e51b8152600401610b3591906150af565b5060078054829003905560008381526005602052604080822091825560019190910180546001600160881b03191690555183907f056bed1bad5d90aa1df671c8d28b1bbb82ff67fd545f804e63cd261140b11f7b90611b9c9084815260200190565b60405180910390a2505050565b6004546001600160a01b0316611bbd612909565b6001600160a01b03161480611bf45750611bd76008612c1b565b546001600160a01b0316611be9612909565b6001600160a01b0316145b6040518060600160405280602f815260200161589e602f913990611c2b5760405162461bcd60e51b8152600401610b3591906150af565b50600c5415156040518060600160405280603181526020016157c66031913990611c685760405162461bcd60e51b8152600401610b3591906150af565b50604080518082018252600a546001600160a01b0316815281518083019092526001600160801b03198416825261ffff8316602083810191909152810191909152611cb5906111b2612044565b604080516001600160801b03198416815261ffff831660208201527fc50d2cc66f62a75104edeabfbcedb2a37ebd1cb8ce0b873ec3afa504e8bb1480910161173d565b6004546001600160a01b0316611d0c612909565b6001600160a01b03161480611d435750611d266008612c1b565b546001600160a01b0316611d38612909565b6001600160a01b0316145b6040518060600160405280602f815260200161589e602f913990611d7a5760405162461bcd60e51b8152600401610b3591906150af565b50611d9b611d8a858585856136e2565b611d92612044565b600d919061380c565b6040805185815260208101859052908101839052606081018290527ffc3ccf3ca5a38fad8786ed39386de19cb574b06205422992c583e4753aa6268f90608001610dd0565b6004546001600160a01b0316611df4612909565b6001600160a01b03161480611e2b5750611e0e6008612c1b565b546001600160a01b0316611e20612909565b6001600160a01b0316145b6040518060600160405280602f815260200161589e602f913990611e625760405162461bcd60e51b8152600401610b3591906150af565b506110c881600360009054906101000a90046001600160a01b03166001600160a01b031663cb37f3b26040518163ffffffff1660e01b8152600401602060405180830381865afa158015611eba573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ede91906150c2565b6138fa565b6003546001600160a01b0316611ef7612909565b6001600160a01b0316146040518060600160405280603381526020016153166033913990611f385760405162461bcd60e51b8152600401610b3591906150af565b506000546040516370a0823160e01b81523060048201526001600160a01b03909116906370a0823190602401602060405180830381865afa158015611f81573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611fa591906150df565b8111156040518060600160405280603781526020016153c76037913990611fdf5760405162461bcd60e51b8152600401610b3591906150af565b5060068190556040518181527f321b800465e47508430424e6c089344256e17f9a5b0b43c751dffbdfd3930263906020015b60405180910390a150565b600060008051602061549c83398151915261148a6000805160206154fc833981519152613a4c565b6000610adb600360009054906101000a90046001600160a01b03166001600160a01b031663d47ae89c6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561209c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120c091906150df565b610e10612a6b565b6003546001600160a01b03166120dc612909565b6001600160a01b031614604051806060016040528060338152602001615316603391399061211d5760405162461bcd60e51b8152600401610b3591906150af565b506110c881613ab4565b6004546001600160a01b031661213b612909565b6001600160a01b0316148061217257506121556008612c1b565b546001600160a01b0316612167612909565b6001600160a01b0316145b6040518060600160405280602f815260200161589e602f9139906121a95760405162461bcd60e51b8152600401610b3591906150af565b50600354604080516060810190915260238082526001600160a01b0390921615916153fe6020830139906121f05760405162461bcd60e51b8152600401610b3591906150af565b50600380546001600160a01b0319166001600160a01b03831690811790915560408051633013ce2960e01b81529051633013ce29916004808201926020929091908290030181865afa15801561224a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061226e91906150c2565b600080546001600160a01b0319166001600160a01b0392831690811790915560408051928416835260208301919091527fb84a8bf26086331c9421c7ce6650da3b0ef748bd335c3f00ada56f481e4321939101612011565b6003546001600160a01b03166122da612909565b6001600160a01b031614604051806060016040528060338152602001615316603391399061231b5760405162461bcd60e51b8152600401610b3591906150af565b50600080546040516370a0823160e01b81523060048201526001600160a01b03909116906370a0823190602401602060405180830381865afa158015612365573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061238991906150df565b9050600360009054906101000a90046001600160a01b03166001600160a01b0316632cc326416040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156123db57600080fd5b505af11580156123ef573d6000803e3d6000fd5b5050600080546040516370a0823160e01b81523060048201529193508492506001600160a01b0316906370a0823190602401602060405180830381865afa15801561243e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061246291906150df565b61246c919061510e565b905061247a601c8483613b81565b827f2c6a6ee02f179bb5f13a4b7fdc9ccd1dd37e524b48ff0957a931348cc9ad75c082604051611b9c91815260200190565b6003546001600160a01b03166124c0612909565b6001600160a01b03161460405180606001604052806033815260200161531660339139906125015760405162461bcd60e51b8152600401610b3591906150af565b506000600560008481526020019081526020016000206000015490506000811160405180606001604052806023815260200161547960239139906125585760405162461bcd60e51b8152600401610b3591906150af565b508082106040518060600160405280603b81526020016156d7603b9139906125935760405162461bcd60e51b8152600401610b3591906150af565b508181036125a1848261299b565b80600754101560405180606001604052806029815260200161582260299139906125de5760405162461bcd60e51b8152600401610b3591906150af565b50600780548290039055600083900361261857600084815260056020526040812090815560010180546001600160881b031916905561262a565b60008481526005602052604090208390555b837f8274e5392b3ab6bba68ee0567f4ede55e361239b8d0122fa5d3b5dc36497b91d846040516117df91815260200190565b60008060008051602061549c833981519152610ac8565b6003546001600160a01b0316612687612909565b6001600160a01b03161460405180606001604052806033815260200161531660339139906126c85760405162461bcd60e51b8152600401610b3591906150af565b50600080546040516370a0823160e01b81523060048201526001600160a01b03909116906370a0823190602401602060405180830381865afa158015612712573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061273691906150df565b9050600360009054906101000a90046001600160a01b03166001600160a01b0316632cc326416040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561278857600080fd5b505af115801561279c573d6000803e3d6000fd5b5050600080546040516370a0823160e01b81523060048201529193508492506001600160a01b0316906370a0823190602401602060405180830381865afa1580156127eb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061280f91906150df565b612819919061510e565b9050827f2c6a6ee02f179bb5f13a4b7fdc9ccd1dd37e524b48ff0957a931348cc9ad75c082604051611b9c91815260200190565b600080546040516370a0823160e01b815230600482015282916128c8916001600160a01b03909116906370a0823190602401602060405180830381865afa15801561289c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128c091906150df565b600654612a6b565b600754909150836128e8576128db61265c565b6128e59082615121565b90505b808210156128f7576000612901565b612901818361510e565b949350505050565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633148015612944575060143610155b15612956575060131936013560601c90565b503390565b60408051808201909152601b81526000805160206154218339815191526020820152816115d75760405162461bcd60e51b8152600401610b3591906150af565b6000828152600560205260409020600101546001600160801b03811690600160801b900460ff166129da8383836129d2600d6129e0565b929190613c6c565b50505050565b60004282600e015411156129f457816129f9565b816007015b92915050565b6000612a0d83600501613a4c565b15612a3e578115612a23575060018201546129f9565b60018301546003840154612a379190612a6b565b90506129f9565b8115612a5057612a3783600101613c9a565b612a37612a5f84600101613c9a565b610ba985600301613c9a565b6000818310612a7a5781612a7c565b825b9392505050565b612a8c83613cbc565b612a968383613cee565b80612aa557612aa58383613cfb565b505050565b6000805160206154dc833981519152805482908290600090612acd908490615121565b909155505080546040805184815260208101929092527ff3a2c404b3362c4238317c3dad1020c2e9e984ba8df38a4a1ff18d57e92a242a910161173d565b600080546040516370a0823160e01b81523060048201526001600160a01b03909116906370a0823190602401602060405180830381865afa158015612b54573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b7891906150df565b905080600654146040518060600160405280603381526020016157656033913990612bb65760405162461bcd60e51b8152600401610b3591906150af565b5060408051808201909152601f81526000805160206154bc833981519152602082015282821015612bfa5760405162461bcd60e51b8152600401610b3591906150af565b50600680548390039055600054612aa5906001600160a01b03168484613d08565b60004282600401541115612c2f57816129f9565b5060020190565b60408051602081019091527f3fc394ec90f88dd7e18c46ffeef55f1b6719a2027f4ae9fbc4ab1182718912008054825290600090612c7c90612c7790613d5a565b613db2565b90506000612cc08286868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250613ddf92505050565b9050856001600160a01b0316816001600160a01b0316146040518060600160405280602b81526020016157f7602b913990612d0e5760405162461bcd60e51b8152600401610b3591906150af565b508254836000612d1d83615134565b9190505550505050505050565b600060008051602061549c8339815191528181556000805160206154fc83398151915282905560405190917f8892a543ee6d561b73cc067b2cde5a76c251a4e0b978d92390f1301db5d125ab91a150565b60408051808201909152601b8152600080516020615421833981519152602082015282612dbb5760405162461bcd60e51b8152600401610b3591906150af565b50612dc46113fd565b8211156040518060400160405280601f81526020016000805160206154bc83398151915281525090612e095760405162461bcd60e51b8152600401610b3591906150af565b5060008051602061549c833981519152828155612e346000805160206154fc83398151915283613e09565b60018101546040805185815260208101929092527ffa4870dc2e2f95d11863e7e765a98e12fe92c3d1d2a703b4cf983f9c4b1052e691015b60405180910390a1505050565b602082015151600384015460801b6001600160801b03199081169116141580612ebc575060208083015101516003840154600160801b900461ffff908116911614155b80612ed95750815160028401546001600160a01b03908116911614155b60405180606001604052806027815260200161568e6027913990612f105760405162461bcd60e51b8152600401610b3591906150af565b506004830154421015612f895781516002840180546001600160a01b039092166001600160a01b03199092169190911790556020808301518051600386018054929093015161ffff16600160801b026001600160901b031990921660809190911c17179055612f7f8142615121565b6004840155505050565b60028301805484546001600160a01b03199081166001600160a01b0380841691909117875560038701805460018901805461ffff600160801b808504821681026001600160901b03199384166001600160801b03871617179093558a51909516959096169490941790955560208088015180519101519092169092029290931660809390931c92909217179055612f7f8142615121565b60048201541515156040518060600160405280602f81526020016152e7602f91399061305f5760405162461bcd60e51b8152600401610b3591906150af565b5080516002830180546001600160a01b039092166001600160a01b03199092169190911790556020908101518051600384018054929093015161ffff16600160801b026001600160901b031990921660809190911c1717905542600490910155565b600c54421015806130cf5750805b60405180606001604052806035815260200161565960359139906131065760405162461bcd60e51b8152600401610b3591906150af565b50612aa583836131166008612c1b565b9190613e6c565b600560008581526020019081526020016000206000015460001460405180606001604052806029815260200161573c602991399061316e5760405162461bcd60e51b8152600401610b3591906150af565b5061317a838383613e87565b6129da84848484613ec8565b60008051602061549c833981519152805461319f6113fd565b8111156040518060400160405280601f81526020016000805160206154bc833981519152815250906131e45760405162461bcd60e51b8152600401610b3591906150af565b506131ed61201c565b60405180606001604052806038815260200161544160389139906132245760405162461bcd60e51b8152600401610b3591906150af565b50600080835560018301556132398382614067565b826001600160a01b03167fa578b4c05763eb039caef37b2d7b949c22914c3838384c9c73cefb78c4a8ed0a82604051611b9c91815260200190565b826001016000838152602001908152602001600020546000146040518060600160405280602a8152602001615374602a9139906132c45760405162461bcd60e51b8152600401610b3591906150af565b50600082815260018401602052604081208290558354829185916132e9908490615121565b9091555050505050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016148061337a57507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661336e6000805160206155cf833981519152546001600160a01b031690565b6001600160a01b031614155b156110325760405163703e46dd60e11b815260040160405180910390fd5b6004546001600160a01b03166133ac612909565b6001600160a01b031614806133e357506133c66008612c1b565b546001600160a01b03166133d8612909565b6001600160a01b0316145b6040518060600160405280602f815260200161589e602f91399061341a5760405162461bcd60e51b8152600401610b3591906150af565b506001546040516316da1fb760e11b81526001600160a01b03838116600483015290911690632db43f6e90602401602060405180830381865afa158015613465573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613489919061514d565b6040518060600160405280602981526020016158cd60299139906115d75760405162461bcd60e51b8152600401610b3591906150af565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa92505050801561351a575060408051601f3d908101601f19168201909252613517918101906150df565b60015b61354257604051634c9c8ce360e01b81526001600160a01b0383166004820152602401610b35565b6000805160206155cf833981519152811461357357604051632a87526960e21b815260048101829052602401610b35565b612aa583836140a0565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146110325760405163703e46dd60e11b815260040160405180910390fd5b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1028054606091600080516020615541833981519152916136059061516a565b80601f01602080910402602001604051908101604052809291908181526020018280546136319061516a565b801561367e5780601f106136535761010080835404028352916020019161367e565b820191906000526020600020905b81548152906001019060200180831161366157829003601f168201915b505050505091505090565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1038054606091600080516020615541833981519152916136059061516a565b6110326140f6565b6136d86140f6565b6115d7828261413f565b6136ea614a94565b600082116040518060600160405280602e815260200161584b602e9139906137255760405162461bcd60e51b8152600401610b3591906150af565b50506040805160a0810182529485528051808201825293845260006020858101829052808701959095528151808301835293845283850152848101929092528151928301909152600182526060830191909152608082015290565b600e8201541515156040518060600160405280602f81526020016152e7602f9139906137bf5760405162461bcd60e51b8152600401610b3591906150af565b508051600783015560208082015180516008850155810151600984015560408201518051600a8501550151600b830155606081015151600c83015560800151600d82015542600e90910155565b600e830154421015613870578151600784015560208083015180516008860155810151600985015560408301518051600a8601550151600b840155606082015151600c8401556080820151600d8401556138668142615121565b600e840155505050565b600783018054845560088401805460018601556009850180546002870155600a860180546003880155600b870180546004890155600c8801805460058a0155600d8901805460068b01558851909655602080890151805190965594850151909355604087015180519092559201519091556060840151519055608083015190556138668142615121565b6000805160206154dc8339815191526000613913610acf565b825460408051808201909152601b8152600080516020615421833981519152602082015291925090856139595760405162461bcd60e51b8152600401610b3591906150af565b5060408051808201909152601d81527f353430327c446562743a206e6f2066756e647320617661696c61626c650000006020820152826139ac5760405162461bcd60e51b8152600401610b3591906150af565b508085111560405180606001604052806025815260200161556160259139906139e85760405162461bcd60e51b8152600401610b3591906150af565b5060006139f58387612a6b565b845481900385559050613a088582614151565b83546040805183815260208101929092527f75aec94f6ad949cfd9728eb818639f1b0b882089b480409ab73245c06469eef4910160405180910390a1505050505050565b6000613a588254151590565b6040518060400160405280601d81526020017f353534317c54696d6572733a2074696d6572206e6f742061637469766500000081525090613aac5760405162461bcd60e51b8152600401610b3591906150af565b505054421190565b6000805160206154dc833981519152805460408051808201909152601b8152600080516020615421833981519152602082015283613b055760405162461bcd60e51b8152600401610b3591906150af565b50808311156040518060600160405280602581526020016155616025913990613b415760405162461bcd60e51b8152600401610b3591906150af565b5081548390038083556040805185815260208101929092527fb940258db5193d85cafbc591d0c29fad88d804ff8b9ab71c8f9545af2bb2c07c9101612e6c565b60008360010160008481526020019081526020016000205490506000811160405180606001604052806025815260200161551c6025913990613bd65760405162461bcd60e51b8152600401610b3591906150af565b508082146040518060600160405280602581526020016158796025913990613c115760405162461bcd60e51b8152600401610b3591906150af565b5083600001548111156040518060600160405280602a8152602001615712602a913990613c515760405162461bcd60e51b8152600401610b3591906150af565b50835403835550600090815260019091016020526040812055565b613c76848361415b565b156129da57613c886001850184614199565b806129da576129da6003850184614199565b805460018201546000919080821015613cb4576000612901565b900392915050565b613cc881600501613a4c565b156110c857600060028201556000600482015560068101546110c8906005830190613e09565b6115d760018301826141e8565b6115d760038301826141e8565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052612aa5908490614205565b60006040518060600160405280602281526020016156b56022913980516020918201208351604051613d959301918252602082015260400190565b604051602081830303815290604052805190602001209050919050565b60006129f9613dbf614268565b8360405161190160f01b8152600281019290925260228201526042902090565b600080600080613def8686614272565b925092509250613dff82826142bf565b5090949350505050565b60408051808201909152601781527f353534307c54696d6572733a207a65726f2064656c6179000000000000000000602082015281613e5b5760405162461bcd60e51b8152600401610b3591906150af565b50613e668142615121565b90915550565b8254612aa59060018501906001600160a01b03168484614378565b601b5442101580613e955750815b15613eb057612aa58382613ea9600d6129e0565b9190614500565b613ebc600d8483614500565b612aa560148483614500565b613ed18361295b565b60008215613f3c576000613ee5600161284d565b9050848110613ef657849150613f36565b809150857f83067e3478aa8b638f7e34eeecb23875cc3671e615445e892cc22dcd4c8b8df3828703604051613f2d91815260200190565b60405180910390a25b50613f8e565b613f44610acf565b8411156040518060400160405280601f81526020016000805160206154bc83398151915281525090613f895760405162461bcd60e51b8152600401610b3591906150af565b508390505b60408051808201909152601f81527f353331337c4163636f756e743a206e6f2066756e647320746f20626c6f636b00602082015281613fe05760405162461bcd60e51b8152600401610b3591906150af565b50604080516060810182528281526001600160801b03428116602080840191825286151584860190815260008b815260059092529481209351845590516001909301805494511515600160801b026001600160881b03199095169390921692909217929092179091556007805483929061405b908490615121565b90915550505050505050565b806006541015614078576000614086565b80600654614086919061510e565b6006556000546115d7906001600160a01b03168383613d08565b6140a982614562565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a28051156140ee57612aa582826145c7565b6115d761463d565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661103257604051631afcd79f60e31b815260040160405180910390fd5b6141476140f6565b6115d7828261465c565b6115d78282612b0b565b60006005830161416a81613a4c565b156141795760009150506129f9565b6006840154815460009161418c9161510e565b84101592506129f9915050565b80826001015410156040518060600160405280602e8152602001615798602e9139906141d85760405162461bcd60e51b8152600401610b3591906150af565b5060019091018054919091039055565b808260010160008282546141fc9190615121565b90915550505050565b600061421a6001600160a01b038416836146bd565b9050805160001415801561423f57508080602001905181019061423d919061514d565b155b15612aa557604051635274afe760e01b81526001600160a01b0384166004820152602401610b35565b6000610adb6146cb565b600080600083516041036142ac5760208401516040850151606086015160001a61429e8882858561473f565b9550955095505050506142b8565b50508151600091506002905b9250925092565b60008260038111156142d3576142d36151a4565b036142dc575050565b60018260038111156142f0576142f06151a4565b0361430e5760405163f645eedf60e01b815260040160405180910390fd5b6002826003811115614322576143226151a4565b036143435760405163fce698f760e01b815260048101829052602401610b35565b6003826003811115614357576143576151a4565b036115d7576040516335e2f38360e21b815260048101829052602401610b35565b83546040805160608101909152602980825261ffff600160801b90930483169284168311919061539e6020830139906143c45760405162461bcd60e51b8152600401610b3591906150af565b5082825b8261ffff168161ffff161015614482576040516bffffffffffffffffffffffff19606088901b1660208201526001600160f01b031960f083901b1660348201526001600160801b03198316603682015260029060460160408051601f1981840301815290829052614438916151ba565b602060405180830381855afa158015614455573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061447891906150df565b91506001016143c8565b5085546040805160608101909152602180825260809290921b6001600160801b031990811690841614916155ae6020830139906144d25760405162461bcd60e51b8152600401610b3591906150af565b5050845461ffff909216600160801b026001600160901b031990921660809390931c92909217179092555050565b82600001548211156040518060600160405280603b815260200161561e603b91399061453f5760405162461bcd60e51b8152600401610b3591906150af565b5061454983613cbc565b614553838361480e565b80612aa557612aa5838361485d565b806001600160a01b03163b60000361459857604051634c9c8ce360e01b81526001600160a01b0382166004820152602401610b35565b6000805160206155cf83398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b6060600080846001600160a01b0316846040516145e491906151ba565b600060405180830381855af49150503d806000811461461f576040519150601f19603f3d011682016040523d82523d6000602084013e614624565b606091505b50915091506146348583836148af565b95945050505050565b34156110325760405163b398979f60e01b815260040160405180910390fd5b6146646140f6565b6000805160206155418339815191527fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10261469e8482615226565b50600381016146ad8382615226565b5060008082556001909101555050565b6060612a7c8383600061490b565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6146f66149a8565b6146fe614a12565b60408051602081019490945283019190915260608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b600080807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084111561477a5750600091506003905082614804565b604080516000808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa1580156147ce573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166147fa57506000925060019150829050614804565b9250600091508190505b9450945094915050565b61481b6001830182614a56565b60405180606001604052806028815260200161558660289139906148525760405162461bcd60e51b8152600401610b3591906150af565b506115d78282613cee565b61486a6003830182614a56565b6040518060600160405280602f81526020016155ef602f9139906148a15760405162461bcd60e51b8152600401610b3591906150af565b506115d760038301826141e8565b6060826148c4576148bf82614a6b565b612a7c565b81511580156148db57506001600160a01b0384163b155b1561490457604051639996b31560e01b81526001600160a01b0385166004820152602401610b35565b5092915050565b6060814710156149305760405163cd78605960e01b8152306004820152602401610b35565b600080856001600160a01b0316848660405161494c91906151ba565b60006040518083038185875af1925050503d8060008114614989576040519150601f19603f3d011682016040523d82523d6000602084013e61498e565b606091505b509150915061499e8683836148af565b9695505050505050565b6000600080516020615541833981519152816149c26135c6565b8051909150156149da57805160209091012092915050565b815480156149e9579392505050565b7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470935050505090565b600060008051602061554183398151915281614a2c613689565b805190915015614a4457805160209091012092915050565b600182015480156149e9579392505050565b6000614a6183613c9a565b9091111592915050565b805115614a7b5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b6040518060a0016040528060008152602001614ac3604051806040016040528060008152602001600081525090565b8152602001614ae5604051806040016040528060008152602001600081525090565b8152602001614b006040518060200160405280600081525090565b8152602001600081525090565b60008060408385031215614b2057600080fd5b50508035926020909101359150565b6001600160a01b03811681146110c857600080fd5b60008083601f840112614b5657600080fd5b50813567ffffffffffffffff811115614b6e57600080fd5b602083019150836020828501011115614b8657600080fd5b9250929050565b600080600060408486031215614ba257600080fd5b8335614bad81614b2f565b9250602084013567ffffffffffffffff811115614bc957600080fd5b614bd586828701614b44565b9497909650939450505050565b600060208284031215614bf457600080fd5b5035919050565b80356001600160801b031981168114614c1357600080fd5b919050565b803561ffff81168114614c1357600080fd5b600080600080600060808688031215614c4257600080fd5b8535614c4d81614b2f565b9450614c5b60208701614bfb565b9350614c6960408701614c18565b9250606086013567ffffffffffffffff811115614c8557600080fd5b614c9188828901614b44565b969995985093965092949392505050565b80151581146110c857600080fd5b600080600080600060a08688031215614cc857600080fd5b8535945060208601359350614cdf60408701614bfb565b9250614ced60608701614c18565b91506080860135614cfd81614ca2565b809150509295509295909350565b600060208284031215614d1d57600080fd5b8135612a7c81614b2f565b634e487b7160e01b600052604160045260246000fd5b60008060408385031215614d5157600080fd5b8235614d5c81614b2f565b9150602083013567ffffffffffffffff80821115614d7957600080fd5b818501915085601f830112614d8d57600080fd5b813581811115614d9f57614d9f614d28565b604051601f8201601f19908116603f01168101908382118183101715614dc757614dc7614d28565b81604052828152886020848701011115614de057600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b600080600060608486031215614e1757600080fd5b83359250602084013591506040840135614e3081614ca2565b809150509250925092565b60005b83811015614e56578181015183820152602001614e3e565b50506000910152565b60008151808452614e77816020860160208601614e3b565b601f01601f19169290920160200192915050565b60ff60f81b881681526000602060e06020840152614eac60e084018a614e5f565b8381036040850152614ebe818a614e5f565b606085018990526001600160a01b038816608086015260a0850187905284810360c08601528551808252602080880193509091019060005b81811015614f1257835183529284019291840191600101614ef6565b50909c9b505050505050505050505050565b8051825260208082015180518285015290810151604084015250604081015180516060840152602081015160808401525060608101515160a08301526080015160c090910152565b6101e08101614f7b8286614f24565b614f8860e0830185614f24565b826101c0830152949350505050565b60008060008060008060c08789031215614fb057600080fd5b8635614fbb81614b2f565b95506020870135614fcb81614b2f565b95989597505050506040840135936060810135936080820135935060a0909101359150565b6000806040838503121561500357600080fd5b61500c83614bfb565b915061501a60208401614c18565b90509250929050565b6000806000806080858703121561503957600080fd5b5050823594602084013594506040840135936060013592509050565b80516001600160a01b0316825260209081015180516001600160801b03191682840152015161ffff16604090910152565b60e081016150948286615055565b6150a16060830185615055565b8260c0830152949350505050565b602081526000612a7c6020830184614e5f565b6000602082840312156150d457600080fd5b8151612a7c81614b2f565b6000602082840312156150f157600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b818103818111156129f9576129f96150f8565b808201808211156129f9576129f96150f8565b600060018201615146576151466150f8565b5060010190565b60006020828403121561515f57600080fd5b8151612a7c81614ca2565b600181811c9082168061517e57607f821691505b60208210810361519e57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b600082516151cc818460208701614e3b565b9190910192915050565b601f821115612aa5576000816000526020600020601f850160051c810160208610156151ff5750805b601f850160051c820191505b8181101561521e5782815560010161520b565b505050505050565b815167ffffffffffffffff81111561524057615240614d28565b6152548161524e845461516a565b846151d6565b602080601f83116001811461528957600084156152715750858301515b600019600386901b1c1916600185901b17855561521e565b600085815260208120601f198616915b828110156152b857888601518255948401946001909101908401615299565b50858210156152d65787850151600019600388901b60f8161c191681555b5050505050600190811b0190555056fe353530307c44656c6179656453657474696e67733a2076616c756520616c726561647920696e697469616c697a6564353330327c4163636f756e743a206f6e6c792070726f636573736f722063616e2063616c6c20746869732066756e6374696f6e353334307c4163636f756e743a20736574746c656d656e7420706572696f64206973206e6f74206f766572353432307c526566756e64733a20726566756e64207265636f726420616c726561647920657869737473353531307c4f6e6554696d6550617373776f72643a20696e76616c6964204f545020636f756e746572353335307c4163636f756e743a20726573756c74696e672076657269666965642062616c616e636520657863656564732061637475616c353335317c4163636f756e743a2070726f636573736f7220616c726561647920736574353130307c436f6d6d6f6e3a20616d6f756e74206973207a65726f0000000000353431307c44656c617965645769746864726177616c733a207769746864726177616c2074696d656c6f636b206e6f742065787072696564353332307c4163636f756e743a207472616e73616374696f6e206e6f7420666f756e6476d8021c4b979e99674b80f4e0cc10154af0e5f32df05eb51b29a464e2435500353130317c436f6d6d6f6e3a20696e73756666696369656e742066756e6473003486e95df892f06bd37fd38c44e2fee4c4efb6660a66e6eb20ed6d8a167eae0076d8021c4b979e99674b80f4e0cc10154af0e5f32df05eb51b29a464e2435501353432317c526566756e64733a20726566756e64207265636f7264206e6f7420666f756e64a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100353430337c446562743a20616d6f756e7420697320686967686572207468616e2064656274353532317c4163636f756e744c696d6974733a207370656e64206c696d6974206578636565646564353531317c4f6e6554696d6550617373776f72643a20696e76616c6964204f5450360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc353532337c4163636f756e744c696d6974733a206e6f206f7470207370656e64206c696d6974206578636565646564353532307c4163636f756e744c696d6974733a20616d6f756e7420657863656564732073696e676c65207472616e73616374696f6e206c696d6974353331327c4163636f756e743a2063617264206f72204f5450207374617465206368616e676520697320696e2070726f6772657373353530317c44656c6179656453657474696e67733a2076616c756520616c7265616479207365744f776e657273686970416363657074616e63652875696e74323536206e6f6e636529353332317c4163636f756e743a206e657720616d6f756e7420697320657175616c206f7220686967686572207468616e20617574686f72697a6564353432337c526566756e64733a2070656e64696e6720616d6f756e74206578636565647320746f74616c353331307c4163636f756e743a207472616e73616374696f6e20494420616c72656164792075736564353333307c4163636f756e743a2076657269666965642062616c616e6365206e6f7420657175616c20746f2062616c616e6365353533317c5370656e644c696d6974733a20696e73756666696369656e74207370656e7420746f2063616e63656c353335327c4163636f756e743a20636172642077697468206f7470207374617465206e6f7420696e697469616c697a6564353535307c4f776e657273686970416363657074616e63653a20696e76616c6964207369676e6174757265353330347c4163636f756e743a20696e73756666696369656e7420626c6f636b656420616d6f756e74353532347c4163636f756e744c696d6974733a207370656e64206c696d697420706572696f64206973207a65726f353432327c526566756e64733a20696e76616c696420616d6f756e74207265636569766564353330317c4163636f756e743a206f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6e353337307c4163636f756e743a20756e617574686f72697a656420696d706c656d656e746174696f6ea26469706673582212201bf402f34a1057d0d739b2911423245677cc744b072d4af8e7f665bc51781a7f64736f6c63430008160033"; +public class TangemPaymentAccount extends Contract { + public static final String BINARY = "60c06040523462000050576200001e62000018620000f3565b62000116565b604051616e73620002b28239608051818181610fea0152612efb015260a0518181816149490152614b830152616e7390f35b600080fd5b634e487b7160e01b600052604160045260246000fd5b90601f01601f191681019081106001600160401b038211176200008d57604052565b62000055565b90620000aa620000a260405190565b92836200006b565b565b6001600160a01b031690565b90565b6001600160a01b038116036200005057565b90505190620000aa82620000bb565b906020828203126200005057620000b891620000cd565b620000b862007125803803806200010a8162000093565b928339810190620000dc565b620001219062000157565b620000aa620001fa565b620000b890620000ac906001600160a01b031682565b620000b8906200012b565b620000b89062000141565b60805262000168620000aa80808080565b62000173306200014c565b60a052565b620000b89060401c60ff1690565b620000b8905462000178565b620000b8905b6001600160401b031690565b620000b8905462000192565b620000b89062000198906001600160401b031682565b90620001da620000b8620001f692620001b0565b82546001600160401b0319166001600160401b03919091161790565b9055565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00620002268162000186565b6200029f576200023681620001a4565b6001600160401b03919082908116036200024e575050565b81620002807fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2936200029a93620001c6565b604051918291826001600160401b03909116815260200190565b0390a1565b60405163f92ee8a960e01b8152600490fdfe6080604052600436101561001257600080fd5b60003560e01c806274f3561461037c57806301e948ff1461029657806312a168311461037757806316fc32971461037257806319cdeb6f1461036d5780631d6ae778146103685780631f852f4b14610363578063226112801461035e578063246f8b96146103595780632ea63823146103545780633013ce291461034f57806334d6ee4a1461034a578063357a03331461034557806335ba9af81461034057806337a1d4151461033b5780633e0fc2ff1461033657806340345137146103315780634e6d8a731461032c5780634f1ef2861461032757806352d1902d14610322578063563b9b0f1461031d578063572b6c05146103185780636b2a3830146103135780636b4943a71461030e5780636fb7e9c11461030957806370908f21146103045780637917cf6f146102ff5780637b103999146102fa5780637da0a877146102f557806384b0196e146102f057806386f15d09146102eb5780638da5cb5b146102e65780638ef008b8146102e1578063997584a4146102dc5780639cadd226146102d7578063aa37ff1b146102d2578063ad3cb1cc146102cd578063b9603bdf146102c8578063bc77d495146102c3578063be69191d146102be578063c45a0155146102b9578063c470d703146102b4578063c9a557fa146102af578063ce1b1d43146102aa578063d47ae89c146102a5578063dbed8faa146102a0578063dcab7e501461029b578063df7727b414610296578063e2b7202514610291578063e37b81541461028c578063e6e268f414610287578063e96ede8e14610282578063eec4f8121461027d578063f2bcd022146102785763f93184c10361038c57611620565b611605565b6115ed565b6115d4565b6115b9565b611588565b611496565b6103c7565b61147b565b611463565b611448565b61142d565b611406565b6113eb565b6113d0565b6113a9565b611390565b611378565b611351565b6112e3565b6112c4565b611247565b61120c565b611191565b61116a565b611136565b610fd5565b610fba565b610f93565b610f56565b610bb9565b610b17565b610af3565b610ac6565b610a93565b610a11565b6109fd565b61093d565b6108f5565b610879565b610861565b610846565b61082d565b6107e3565b610748565b6106c6565b6105e1565b6105c9565b6105b0565b61052c565b6104c0565b610486565b610441565b610397565b600091031261038c57565b600080fd5b9052565b565b3461038c576103a7366004610381565b6103c36103b261163c565b6040515b9182918290815260200190565b0390f35b3461038c576103d7366004610381565b6103c36103b2611655565b6001600160a01b031690565b90565b6001600160a01b0381165b0361038c57565b90503590610395826103f1565b9081604091031261038c5790565b919060608382031261038c576103ee90602061043a8286610403565b9401610410565b3461038c5761045a61045436600461041e565b90611869565b604051005b806103fc565b905035906103958261045f565b9060208282031261038c576103ee91610465565b3461038c5761045a610499366004610472565b611938565b6103ee916008021c81565b906103ee915461049e565b6103ee600060076104a9565b3461038c576104d0366004610381565b6103c36103b26104b4565b8015156103fc565b90503590610395826104db565b60808183031261038c576105048282610403565b926103ee6105158460208501610465565b9360606105258260408701610465565b94016104e3565b3461038c5761045a61053f3660046104f0565b92919091611a30565b909182601f8301121561038c578135916001600160401b03831161038c57602001926001830284011161038c57565b91909160408184031261038c5761058e8382610403565b9260208201356001600160401b03811161038c576105ac9201610548565b9091565b3461038c5761045a6105c3366004610577565b91611c61565b3461038c576105d9366004610381565b61045a611c82565b3461038c576105f1366004610381565b61045a611cda565b634e487b7160e01b600052604160045260246000fd5b90601f01601f191681019081106001600160401b0382111761063057604052565b6105f9565b9061039561064260405190565b928361060f565b919060808382031261038c5761069c9060606106656080610635565b946106708382610465565b865261067f8360208301610465565b60208701526106918360408301610465565b604087015201610465565b6060830152565b919060a08382031261038c576103ee9060206106bf8286610403565b9401610649565b3461038c5761045a6106d93660046106a3565b90611d9b565b6103ee916008021c6001600160a01b031690565b906103ee91546106df565b6103ee6000806106f3565b6103ee906103e2906001600160a01b031682565b6103ee90610709565b6103ee9061071d565b61039190610726565b602081019291610395919061072f565b3461038c57610758366004610381565b6103c36107636106fe565b60405191829182610738565b0190565b9061079361078c610782845190565b8084529260200190565b9260200190565b9060005b8181106107a45750505090565b9091926107ca6107c360019286516001600160a01b0316815260200190565b9460200190565b929101610797565b60208082526103ee92910190610773565b3461038c576107f3366004610381565b6103c36107fe611da5565b604051918291826107d2565b919060408382031261038c576103ee9060206108268286610403565b9401610465565b3461038c5761045a61084036600461080a565b90611dd1565b3461038c57610856366004610381565b6103c36103b2611e24565b3461038c57610871366004610381565b61045a611fd5565b3461038c5761045a61088c36600461080a565b906122fe565b908160e091031261038c5790565b90916101808284031261038c576108b78383610403565b926108c58160208501610403565b926108d38260408301610403565b926103ee6108e48460608501610403565b936101606108268260808701610892565b3461038c5761045a6109083660046108a0565b9493909392919261286e565b909160608284031261038c576103ee61092d8484610403565b9360406108268260208701610465565b3461038c5761045a610950366004610914565b91612c73565b6001600160401b03811161063057602090601f01601f19160190565b90826000939282370152565b9092919261099361098e82610956565b610635565b938185528183011161038c57610395916020850190610972565b9080601f8301121561038c578160206103ee9335910161097e565b91909160408184031261038c576109df8382610403565b9260208201356001600160401b03811161038c576103ee92016109ad565b61045a610a0b3660046109c8565b90612c9e565b3461038c57610a21366004610381565b6103c36103b2612ceb565b91906101408382031261038c57610a438184610403565b92610a518260208301610892565b926101008201356001600160401b03811161038c5783610a72918401610548565b9290936101208201356001600160401b03811161038c576105ac9201610548565b3461038c5761045a610aa6366004610a2c565b94939093929192612ee5565b9060208282031261038c576103ee91610403565b3461038c576103c3610ae1610adc366004610ab2565b612ef3565b60405191829182901515815260200190565b3461038c5761045a610b06366004610472565b613063565b6103ee600060086104a9565b3461038c57610b27366004610381565b6103c36103b2610b0b565b6001600160801b031981166103fc565b9050359061039582610b32565b61ffff81166103fc565b9050359061039582610b4f565b909160c08284031261038c57610b7c8383610403565b92610b8a8160208501610465565b92610b988260408301610465565b926103ee610ba98460608501610b42565b9360a06105258260808701610b59565b3461038c5761045a610bcc366004610b66565b94939093929192613103565b90610be290610726565b600052602052604060002090565b6103ee905b60ff1690565b6103ee9054610bf0565b6103ee9060081c610bf5565b6103ee9054610c05565b6103ee9060101c5b6001600160401b031690565b6103ee9054610c1b565b6103ee9060801c5b61ffff1690565b6103ee9054610c39565b6103ee6040610635565b90610395610c946000610c6d610c52565b94610c8e610c7e8383015460801b90565b6001600160801b03191687840152565b01610c48565b61ffff166020840152565b6103ee9081565b6103ee9054610c9f565b6103ee6060610635565b90610395610cf86002610ccb610cb0565b94610cdc610cd882610c5c565b8752565b610cf2610ceb60018301610c5c565b6020880152565b01610ca6565b6040840152565b90610395610d1d6001610d10610c52565b94610cf2610cd882610ca6565b6020840152565b6103ee6020610635565b90610395610d466000610d3f610d24565b9401610ca6565b8352565b6103ee60a0610635565b90610395610dad6006610d65610d4a565b94610d72610cd882610ca6565b610d81610ceb60018301610cff565b610d97610d9060038301610cff565b6040880152565b610cf2610da660058301610d2e565b6060880152565b6080840152565b90610395610cf8600e610dc5610cb0565b94610dd2610cd882610d54565b610cf2610ceb60078301610d54565b610dec906005610bd8565b610df581610bfb565b91610dff82610c11565b91610e0981610c2f565b916103ee6004610e1b60018501610cba565b9301610db4565b805180516001600160801b031916835260209081015161ffff16908301526103959190608090604090610e7460208201518386019080516001600160801b031916825260209081015161ffff16910152565b0151910152565b8051825261039591906020908190610e74565b516103959152565b80518252610395919060c090608090610eb760208201516020860190610e7b565b610ec960408201516060860190610e7b565b610e74606082015160a0860190610e8e565b906101c0604061039593610ef760008201516000860190610e96565b610e74602082015160e0860190610e96565b90151581526102e08101959461039594909361010093610f4f9291610f4591610f359015156020870152565b6001600160401b03166040850152565b6060830190610e22565b0190610edb565b3461038c576103c3610f71610f6c366004610ab2565b610de1565b91610f7e95939560405190565b95869586610f09565b6103ee600060096104a9565b3461038c57610fa3366004610381565b6103c36103b2610f87565b6103ee600060026106f3565b3461038c57610fca366004610381565b6103c3610763610fae565b3461038c57610fe5366004610381565b6103c37f00000000000000000000000000000000000000000000000000000000000000005b6040515b918291826001600160a01b03909116815260200190565b60005b8381106110385750506000910152565b8181015183820152602001611028565b61106961107260209361076f9361105d815190565b80835293849260200190565b95869101611025565b601f01601f191690565b9061108b61078c610782845190565b9060005b81811061109c5750505090565b9091926110b26107c36001928651815260200190565b92910161108f565b9395919461111261110a611129956110fc611122956103ee9c9a6110ef60e08c019260008d01906001600160f81b0319169052565b8a820360208c0152611048565b9088820360408a0152611048565b976060870152565b6001600160a01b03166080850152565b60a0830152565b60c081840391015261107c565b3461038c57611146366004610381565b6103c36111516131a1565b9361116197959793919360405190565b978897886110ba565b3461038c5761117a366004610381565b6103c3610ae161323f565b6103ee600060046106f3565b3461038c576111a1366004610381565b6103c361100a611185565b9160a08383031261038c576111c18284610403565b926111cf8360208301610465565b926111dd8160408401610465565b9260608301356001600160401b03811161038c57826112036080946103ee938701610548565b949095016104e3565b3461038c5761045a61121f3660046111ac565b9493909392919261330a565b919060408382031261038c576103ee9060206105258286610403565b3461038c5761045a61125a36600461122b565b906134ab565b919060808382031261038c576112768184610403565b9261128482602083016104e3565b9260408201356001600160401b03811161038c57836112a4918401610548565b92909360608201356001600160401b03811161038c576105ac9201610548565b3461038c5761045a6112d7366004611260565b949390939291926135ea565b3461038c5761045a6112f6366004610ab2565b6136c8565b9061130861098e83610956565b918252565b61131760056112fb565b640352e302e360dc1b602082015290565b6103ee61130d565b6103ee611328565b6103ee611330565b60208082526103ee92910190611048565b3461038c57611361366004610381565b6103c361136c611338565b60405191829182611340565b3461038c5761045a61138b366004610472565b613745565b3461038c5761045a6113a336600461080a565b906138cf565b3461038c576113b9366004610381565b6103c3610ae16138d9565b6103ee600060016106f3565b3461038c576113e0366004610381565b6103c36107636113c4565b3461038c576113fb366004610381565b6103c36103b2613902565b3461038c57611416366004610381565b6103c36103b261392d565b6103ee600060036106f3565b3461038c5761143d366004610381565b6103c3610763611421565b3461038c57611458366004610381565b6103c36103b2613963565b3461038c5761045a611476366004610472565b6139e1565b3461038c5761148b366004610381565b6103c36103b26139ea565b3461038c576114a6366004610381565b6103c36103b2613a13565b6103ee6103ee6103ee9290565b90610be2906114b1565b6103ee906103e2565b6103ee90546114c8565b6103ee9060a01c610c23565b6103ee90546114db565b6103ee9060e01c610bf5565b6103ee90546114f1565b6115129060066114be565b9061151c82610ca6565b91611529600182016114d1565b916103ee600161153a8185016114e7565b93016114fd565b61158061039594611570606094989795611560608086019a6000870152565b6001600160a01b03166020850152565b6001600160401b03166040830152565b019015159052565b3461038c576103c36115a361159e366004610472565b611507565b906115b094929460405190565b94859485611541565b3461038c576115c9366004610381565b6103c36103b2613a3e565b3461038c5761045a6115e7366004610914565b91613c48565b3461038c5761045a611600366004610472565b613c92565b3461038c57611615366004610381565b6103c361100a613c9b565b3461038c576103c36103b261163636600461080a565b90613d3b565b6103ee6000600080516020616dfe833981519152610cf2565b6103ee6000613d47565b611669602f6112fb565b7f353330317c4163636f756e743a206f6e6c79206f776e65722063616e2063616c60208201526e36103a3434b990333ab731ba34b7b760891b604082015290565b6103ee61165f565b6103ee6116aa565b156116c25750565b6116e4906116cf60405190565b62461bcd60e51b815291829160048301611340565b0390fd5b90610395916117276116f8613de3565b6117056103e260046114d1565b6001600160a01b0382161490811561172c575b506117216116b2565b906116ba565b611803565b61174491506103ee61173f916005610bd8565b613e1c565b38611718565b919060408382031261038c576117799060206117666040610635565b946117718382610b42565b865201610b59565b6020830152565b6103ee90369061174a565b506103ee906020810190610b42565b506103ee906020810190610b59565b9060206117da610395936117d16117c3600083018361178b565b6001600160801b0319168552565b8281019061179a565b61ffff16910152565b6001600160a01b03909116815260608101929161039591602001906117a9565b907fec402263d6c2da14e65d81be89696eecf02dad51b4edf01a00f7f001fdae65839161184f6118376103ee836005610bd8565b61183f613963565b9061184985611780565b90613e88565b61186461185b60405190565b928392836117e3565b0390a1565b90610395916116e8565b610395906118826116f8613de3565b6118b4565b90505190610395826103f1565b9060208282031261038c576103ee91611887565b6040513d6000823e3d90fd5b6118c66118c160036114d1565b610726565b9060206118d260405190565b63659bf9d960e11b815292839060049082905afa908115611933576118ff92600092611902575b50613f43565b50565b61192591925060203d60201161192c575b61191d818361060f565b810190611894565b90386118f9565b503d611913565b6118a8565b61039590611873565b61194b60336112fb565b7f353330327c4163636f756e743a206f6e6c792070726f636573736f722063616e6020820152721031b0b636103a3434b990333ab731ba34b7b760691b604082015290565b6103ee611941565b6103ee611990565b906103959392916119db6119b2613de3565b6119d26119c56103e26118c160036114d1565b916001600160a01b031690565b14611721611998565b9192611a21611a1b7f6f9b3fed74d79ecbab600aa9408ec793100801e0e811317a78db1cd7f56c589893611a166000611a2b9589848a614050565b6114b1565b93610726565b936103b660405190565b0390a3565b906103959392916119a0565b906103959291611a4d6116f8613de3565b611ac2565b6103e26103ee6103ee9290565b6103ee90611a52565b906001600160a01b03905b9181191691161790565b90611a8d6103ee611a9492610726565b8254611a68565b9055565b6001600160a01b039091168152604081019291610395916020905b01906001600160a01b03169052565b91611ad691611ad084614135565b836141c4565b611ae060046114d1565b600090611aef6103e283611a5f565b6001600160a01b03821603611bb75750611b0a826004611a7d565b611b176118c160026114d1565b91823b1561038c5781611b2960405190565b630d00929760e01b81526001600160a01b0383166004820152938490602490829084905af1918215611933577f50146d0e3c60aa1d17a70635b05494f864e86144a2201275021014fbf08bafe29361186493611b8a575b505060405161100e565b81611ba992903d10611bb0575b611ba1818361060f565b810190610381565b3880611b80565b503d611b97565b91611bc3816004611a7d565b611bd06118c160026114d1565b803b1561038c57818391611c079583611be860405190565b809881958294611bfc63f00d4b5d60e01b90565b845260048401611a98565b03925af1918215611933577f50146d0e3c60aa1d17a70635b05494f864e86144a2201275021014fbf08bafe29361186493611c44575b505061100a565b81611c5a92903d10611bb057611ba1818361060f565b3880611c3d565b906103959291611a3c565b611c776116f8613de3565b610395610395614254565b610395611c6c565b611c956116f8613de3565b6103957f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da611864611cc660046114d1565b61100a611cd36000611a5f565b6004611a7d565b610395611c8a565b9061039591611cf26116f8613de3565b611d43565b8051825261039591906060908190611d1460208201516020860152565b610e7460408201516040860152565b6001600160a01b03909116815260a0810192916103959160200190611cf7565b907f92daafee7b0d3ce63f680acb8054b3dc8e73ffb341502efa1b45a15b1687752991611d86611d776103ee836005610bd8565b83611d80613963565b916142de565b611864611d9260405190565b92839283611d23565b9061039591611ce2565b6103ee600a614307565b9061039591611dbf6116f8613de3565b61039591611dcb613963565b91614360565b9061039591611daf565b905051906103958261045f565b9060208282031261038c576103ee91611ddb565b634e487b7160e01b600052601160045260246000fd5b91908203918211611e1f57565b611dfc565b611e706020611e366118c160006114d1565b611e3f30610726565b90611e4960405190565b938492839182916370a0823160e01b5b83526001600160a01b031660048301526024820190565b03915afa90811561193357600091611eb6575b50611e916103ee6008610ca6565b8110611eab576103ee90611ea56008610ca6565b90611e12565b506103ee60006114b1565b611ed8915060203d602011611ede575b611ed0818361060f565b810190611de8565b38611e83565b503d611ec6565b611eef603c6112fb565b7f353330337c4163636f756e743a206f6e6c792070726f636573736f72206f722060208201527f6f776e65722063616e2063616c6c20746869732066756e6374696f6e00000000604082015290565b6103ee611ee5565b6103ee611f3e565b611f8c611f59613de3565b611f696103e26118c160036114d1565b6001600160a01b03821614908115611fad575b8115611f94575b50611721611f46565b610395611fcd565b611fa791506103ee61173f916005610bd8565b38611f83565b9050611fbc6103e260046114d1565b6001600160a01b0382161490611f7c565b61039561445f565b610395611f4e565b9061039591611fed6119b2613de3565b6121f4565b81810292918115918404141715611e1f57565b6103ee6127106114b1565b634e487b7160e01b600052601260045260246000fd5b90612030565b9190565b90811561203b570490565b612010565b61204a60236112fb565b7f353334317c4163636f756e743a207472616e73616374696f6e206e6f7420666f6020820152621d5b9960ea1b604082015290565b6103ee612040565b6103ee61207f565b612099603e6112fb565b7f353338327c4163636f756e743a207472616e73616374696f6e2077617320617560208201527f74686f72697a65642077697468206120646966666572656e7420636172640000604082015290565b6103ee61208f565b6103ee6120e8565b61210260296112fb565b7f353330347c4163636f756e743a20696e73756666696369656e7420626c6f636b602082015268195908185b5bdd5b9d60ba1b604082015290565b6103ee6120f8565b6103ee61213d565b9060001990611a73565b906121676103ee611a94926114b1565b825461214d565b91908201809211611e1f57565b9160001960089290920291821b911b611a73565b91906121a06103ee611a94936114b1565b90835461217b565b6103959160009161218f565b6000906001906121c483826121a8565b0155565b634e487b7160e01b600052600060045260246000fd5b906000036121ef57610395906121b4565b6121c8565b7fec2d267131598e18b6231352c72c82d77f6236d7da371ad09865737055a23180611a2b611a21611a1b6001956122e96122e26122356103ee8460066114be565b6122a661228b61224483610ca6565b9b8c9361228561226e61226061225a6009610ca6565b88611ff2565b612268612005565b90612026565b9561227c61202c60006114b1565b11611721612087565b016114d1565b61229d6001600160a01b038c166119c5565b146117216120f0565b6122c06122b36008610ca6565b8b905b1015611721612145565b6122dc6122d58b6122d16008610ca6565b0390565b6008612157565b8961216e565b87836144e9565b611a1660006122f98360066114be565b6121de565b9061039591611fdd565b6103ee9060401c610bf5565b6103ee9054612308565b6103ee90610c23565b6103ee905461231e565b610c236103ee6103ee9290565b906001600160401b0390611a73565b610c236103ee6103ee926001600160401b031690565b906123736103ee611a949261234d565b825461233e565b9068ff00000000000000009060401b611a73565b9061239e6103ee611a9492151590565b825461237a565b61039190612331565b60208101929161039591906123a5565b9093949291926123eb7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b9586936124076124016123fd87612314565b1590565b95612327565b9560009761241489612331565b6001600160401b038916148061250c575b6001986124416124348b612331565b916001600160401b031690565b1490816124e8575b155b90816124df575b506124cd5761247b95876124728b6124698c612331565b9d019c8d612363565b6124be576126b7565b61248457505050565b6124b2611864927fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29461238e565b604051918291826123ae565b6124c8898c61238e565b6126b7565b60405163f92ee8a960e01b8152600490fd5b15905038612452565b905061244b6124f630610726565b3b61250361202c8d6114b1565b14919050612449565b5086612425565b61251d60146112fb565b7315185b99d95b54185e5b595b9d1058d8dbdd5b9d60621b602082015290565b6103ee612513565b61254f60016112fb565b603160f81b602082015290565b6103ee612545565b919060e08382031261038c576125a490606061257f81610635565b9461258a83826104e3565b8652612599836020830161174a565b602087015201610649565b6040830152565b6103ee903690612564565b6103fc816114c8565b90505190610395826125b6565b9060208282031261038c576103ee916125bf565b506103ee9060208101906104e3565b506103ee906020810190610465565b9060606126536103959361261c61261860008301836125ef565b8552565b61263361262c60208301836125ef565b6020860152565b61264a61264360408301836125ef565b6040860152565b828101906125ef565b910152565b90606061268e6103959361267861267260008301836125e0565b15158552565b6126896020820160208601906117a9565b820190565b9101906125fe565b6001600160a01b039091168152610100810192916103959160200190612658565b90612752612759936118c161274b611cd3946126d287614135565b6126db81614135565b6126e484614135565b6126ed89614135565b6126f5614547565b6126fd614547565b61271661270861253d565b61271061255c565b90614566565b61271e614547565b612726614547565b61272e614547565b612736614547565b6118c16127446118c1613de3565b6001611a7d565b6003611a7d565b6002611a7d565b61277961276a6103ee836005610bd8565b612773846125ab565b9061460c565b61278481600a614696565b506127926118c160036114d1565b92602061279e60405190565b633013ce2960e01b815294859060049082905afa908115611933576127f26127f9927f778238c396424814c7113c41823459473262293bc693b847480fcbc85a6d94bd9660009161283f575b506000611a7d565b6009612157565b7f50146d0e3c60aa1d17a70635b05494f864e86144a2201275021014fbf08bafe261282761100a60046114d1565b0390a161186461283660405190565b92839283612696565b612861915060203d602011612867575b612859818361060f565b8101906125cc565b386127ea565b503d61284f565b9061039595949392916123be565b90610395929161288d6119b2613de3565b612907565b61289c60206112fb565b7f353338307c4163636f756e743a2063617264206973206e6f7420616374697665602082015290565b6103ee612892565b6103ee6128c5565b9081526040810192916103959160200152565b0152565b9081526060810193926103959290916040916128e890611779565b9190916129186103ee8460066114be565b9161292281614706565b61296f6001840193612933856114d1565b9060009586926129486119c56103e286611a5f565b03612c505750610cf26129676129626103ee886005610bd8565b614720565b6117216128cd565b926129866122606129806009610ca6565b86611ff2565b93600094818411612b2b576129dd916129ad6122d5928698506129a887840390565b61216e565b6129b6856114b1565b8111612b1a575b505b6129d36129cc6008610ca6565b82906122b6565b6122d16008610ca6565b6129ec816122f98760066114be565b6129f96118c160036114d1565b916020612a0560405190565b63659bf9d960e11b815293849060049082905afa938415611933577fabb0c23c76702883b6d5427581380669e510735331d9dc6a0eb99a1312dd5d5a86612a68611a1b612a869a611a1660209b611e369b6118c19b600091612afc575b506148a6565b93612a7e612a7560405190565b928392836128d5565b0390a36114d1565b03915afa8015611933577fc638d270f17f849b0216682c6cf6ee6fd9af6cac195b4647eb3b4857bbc10dc791600091612add575b50612ac56008610ca6565b90611864612ad161163c565b604051938493846128ec565b612af6915060203d602011611ede57611ed0818361060f565b38612aba565b612b1491508e3d60201161192c5761191d818361060f565b38612a62565b612b2590878a6144e9565b386129bd565b948195612b46612b41600461076f896005610bd8565b614741565b90612b52858301610ca6565b848491612b5c8190565b8210612c28575b5050612b6e866114b1565b8111612baf575b5050506129dd916122d59150612b8a87860390565b612b93856114b1565b8111612ba0575b506129bf565b612ba9906147fd565b38612b9a565b6129dd9492985090612bce6122d59492612bc8858a0390565b906147c7565b868482019a83831015612bf9575050612bea90612bf192611e12565b888b6144e9565b918193612b75565b9091838111612c0c575b50505050612bf1565b612c1f93612c1991611e12565b906147d4565b38858180612c03565b612c499250612c41919003612bc88461076f8a88614759565b612bc8611655565b8438612b63565b612c5c612c6e916114d1565b61229d6001600160a01b0388166119c5565b610cf2565b90610395929161287c565b9061039591612c8b61493e565b9061039591612c9981614a86565b614a8f565b9061039591612c7e565b6103ee90612cb4614b6d565b612ce2565b6103ee7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6114b1565b506103ee612cb9565b6103ee6000612ca8565b906103959594939291612d096116f8613de3565b612d89565b356103ee816104db565b90505190610395826104db565b9060208282031261038c576103ee91612d18565b612d4360246112fb565b7f353335337c4163636f756e743a20696e76616c6964206361726420636f6e667260208201526334b6b2b960e11b604082015290565b6103ee612d39565b6103ee612d79565b94612db79194612da8612ddb95602095612da28a614135565b896141c4565b612db186612d0e565b87614baf565b612dc46118c160036114d1565b604051938492839182916399fef6bb60e01b611e59565b03915afa801561193357612dfa91600091612eb6575b50611721612d81565b612e14612e0b6103ee846005610bd8565b612773836125ab565b612e1f82600a614696565b50612e2d6118c160026114d1565b91823b1561038c576000612e4060405190565b631ee284a960e11b81526001600160a01b0383166004820152938490602490829084905af1928315611933577f778238c396424814c7113c41823459473262293bc693b847480fcbc85a6d94bd93612ea0575b5061186461283660405190565b612eb0906000611ba1818361060f565b38612e93565b612ed8915060203d602011612ede575b612ed0818361060f565b810190612d25565b38612df1565b503d612ec6565b906103959594939291612cf5565b612f1f6119c57f00000000000000000000000000000000000000000000000000000000000000006103e2565b1490565b61039590612f326119b2613de3565b612fa0565b612f4160376112fb565b7f353335307c4163636f756e743a20726573756c74696e6720766572696669656460208201527f2062616c616e636520657863656564732061637475616c000000000000000000604082015290565b6103ee612f37565b6103ee612f90565b612fe990612fb2816129a86007610ca6565b906020612fc26118c160006114d1565b612fcb30610726565b90612fd560405190565b958692839182916370a0823160e01b611e59565b03915afa801561193357613027613033917fd6004694c2f543b69302481de26731f63c1d41062795b5366d8ca20fd3e3d6d39560009161304a575090565b835b1115611721612f98565b61303e826007612157565b611864612a7560405190565b6103ee915060203d602011611ede57611ed0818361060f565b61039590612f23565b9061039595949392916130806119b2613de3565b93946130cf6130c9859493611a166001856130c1611a2b978d7f30ac254a4e36e31e8baa2471788b94b8879347196809fce466f5c214c7d71e639c8e614c91565b87848c614050565b95610726565b956130d960405190565b938493849081526001600160801b031991909116602082015261ffff909116604082015260600190565b90610395959493929161306c565b1561311857565b60405162461bcd60e51b81526020600482015260156024820152741152540dcc4c8e88155b9a5b9a5d1a585b1a5e9959605a1b6044820152606490fd5b6001600160401b0381116106305760208091020190565b9061130861098e83613155565b369037565b9061039561319461318e8461316c565b93613155565b601f190160208401613179565b600080516020616e1e8339815191526131da6131bc82610ca6565b916000926131cc61202c856114b1565b14908161321f575b50613111565b6131e2614dd3565b916131eb614dee565b916131f530610726565b9061321061320b613205836114b1565b926114b1565b61317e565b600f60f81b9594934693929190565b61322c9150600101610ca6565b61323861202c846114b1565b14386131d4565b6103ee7f76d8021c4b979e99674b80f4e0cc10154af0e5f32df05eb51b29a464e2435502614e07565b90610395959493929161327c6119b2613de3565b6132b7565b9190611072816132988161076f9560209181520190565b8095610972565b9081526040602082018190526103ee93910191613281565b93946132f76130c97f8b7b934d191c6093eafb9a1fc61ef68d088dc5a0438697fe2ceac68b79574a52959493611a166001611a2b956130c18c8a8d614e89565b9561330160405190565b9384938461329f565b906103959594939291613268565b90610395916133286116f8613de3565b613406565b6103ee6103ee6103ee926001600160401b031690565b61334d60266112fb565b7f353338317c4163636f756e743a20636172642064697361626c6520696e2070726020820152656f677265737360d01b604082015290565b6103ee613343565b6103ee613385565b61339f60216112fb565b7f353335347c4163636f756e743a2069734f776e657220616c72656164792073656020820152601d60fa1b604082015290565b6103ee613395565b6103ee6133d2565b9061ff009060081b611a73565b906133ff6103ee611a9492151590565b82546133e2565b6118649061100a7f6a5e3421b4ba058a580be88d10e1c5940843a54923898f281b2fdf1ae733aef893600061343f6103ee856005610bd8565b61344b61296782614720565b0161347e61345882610c2f565b61347561346f6103ee613469613963565b4261216e565b9161332d565b1161172161338d565b6134a66134976000613491876005610bd8565b01610c11565b151583151514156117216133da565b6133ef565b9061039591613318565b9061039595949392916134c96116f8613de3565b6134ed565b6001600160a01b03909116815260408101929161039591602090611580565b94612db7919461350361350a95602095896141c4565b8587614baf565b03915afa80156119335761352891600091612eb65750611721612d81565b61353f8161353a6103ee856005610bd8565b614f34565b61354a82600a614696565b506135586118c160026114d1565b91823b1561038c57600061356b60405190565b631ee284a960e11b81526001600160a01b0383166004820152938490602490829084905af1928315611933577f1526aa7b69cd2d8a922030e75d5e009a330a1e46b715fd6f0e438df811d8ce8b936135d4575b506118646135cb60405190565b928392836134ce565b6135e4906000611ba1818361060f565b386135be565b9061039595949392916134b5565b610395906136076116f8613de3565b6136266136186103ee836005610bd8565b613620613963565b90614fba565b61363181600a615005565b5061363f6118c160026114d1565b90813b1561038c57600061365260405190565b634149177b60e01b81526001600160a01b0383166004820152928390602490829084905af1908115611933577f6a5e3421b4ba058a580be88d10e1c5940843a54923898f281b2fdf1ae733aef892611864926136b2575b5060405161100e565b6136c2906000611ba1818361060f565b386136a9565b610395906135f8565b610395906136e06119b2613de3565b6136f26020611e366118c160006114d1565b03915afa9182156119335761373a6137347f321b800465e47508430424e6c089344256e17f9a5b0b43c751dffbdfd3930263946118649460009161304a575090565b82613029565b6103b2816007612157565b610395906136d1565b906103959161375e6119b2613de3565b61376b6118c160006114d1565b6370a0823161377930610726565b90602061378560405190565b80946137918460e01b90565b82526001600160a01b038516600483015260249082905afa928315611933576000936138ae575b506137c66118c160036114d1565b91823b1561038c5760006137d960405190565b632cc3264160e01b8152938490600490829084905af19182156119335761382a93602093613898575b50611e596138136118c160006114d1565b9161381d60405190565b9586948593849360e01b90565b03915afa91821561193357611a1b613873611a21927faef62a4e4bd58df7ff5d05f9b40355fadfa67884312a36b7f64c53fd699d4f5695611a2b95600091613879575b50611e12565b956114b1565b613892915060203d602011611ede57611ed0818361060f565b3861386d565b6138a8906000611ba1818361060f565b38613802565b6138c891935060203d602011611ede57611ed0818361060f565b91386137b8565b906103959161374e565b6103ee7f76d8021c4b979e99674b80f4e0cc10154af0e5f32df05eb51b29a464e2435502615068565b6103ee60007f3fc394ec90f88dd7e18c46ffeef55f1b6719a2027f4ae9fbc4ab118271891200610cf2565b6103ee60007f5066de352c02692985a663f298a4534cb506e7947f703135018b6c3e10f82e00610cf2565b6103ee610e106114b1565b6139706118c160036114d1565b602061397b60405190565b63351eba2760e21b815291829060049082905afa8015611933576103ee916000916139aa575b50612bc8613958565b6139c3915060203d602011611ede57611ed0818361060f565b386139a1565b610395906139d86119b2613de3565b61039590615093565b610395906139c9565b6103ee7f76d8021c4b979e99674b80f4e0cc10154af0e5f32df05eb51b29a464e2435502610ca6565b6103ee60007fa715e0242b72e6c41603cf400167fb9f6392ccc61630d77addc87403bc041800610cf2565b6103ee6001600080516020616dde833981519152610cf2565b906103959291613a686119b2613de3565b613b25565b613a7760236112fb565b7f353332307c4163636f756e743a207472616e73616374696f6e206e6f7420666f6020820152621d5b9960ea1b604082015290565b6103ee613a6d565b6103ee613aac565b613ac6603b6112fb565b7f353332317c4163636f756e743a206e657720616d6f756e74206973206571756160208201527f6c206f7220686967686572207468616e20617574686f72697a65640000000000604082015290565b6103ee613abc565b6103ee613b15565b90611a2b611a21611a1b7f69cb00da9d035cbf84d93e5abc808a960d34e0da15bbddbda1e24f1b38bab89993613b5f6103ee8260066114be565b613c1c6122d56129bf8a613b7285610ca6565b613b89613b7f60006114b1565b8211611721613ab4565b613bac8c61229d6119c5613b9f60018b016114d1565b926001600160a01b031690565b613bba818310611721613b1d565b6122d1613c15613bd3613bcd6009610ca6565b84611ff2565b611ea5613c0e613c07613bee613be7612005565b8095612026565b93613c02613bfc6009610ca6565b8a611ff2565b612026565b928661216e565b918661216e565b8d896144e9565b87613c2760006114b1565b8103613c3f575050611a1660006122f98360066114be565b611a1691612157565b906103959291613a57565b61039590613c626119b2613de3565b6118647f7c8919e8b0586f5fc7dfabcfce8ecf5465b6ac9f6aaa0bcba610c55e30dad8c3916103b2816009612157565b61039590613c53565b6103ee6000600080516020616dde833981519152612285565b906103ee9291613cc56119b2613de3565b92919250613cec613ce1612b41600461076f6000956005610bd8565b93612bc88386614759565b90613cfa6118c160036114d1565b6020613d0560405190565b63659bf9d960e11b815291829060049082905afa80156119335761039593613d34926000926119025750613f43565b80946147d4565b6103ee91906000613cb4565b613d596020611e366118c160006114d1565b03915afa801561193357613d7a91600091613dc4575b50612bc86007610ca6565b90613d856008610ca6565b90819015613dab575b50808210613d9f576103ee91611e12565b50506103ee60006114b1565b613dbe9150613db8613a3e565b9061216e565b38613d8e565b613ddd915060203d602011611ede57611ed0818361060f565b38613d6f565b613dec33612ef3565b80613e06575b15613e0257601436033560601c90565b3390565b5036613e1561202c60146114b1565b1015613df2565b613e2581610c11565b9081613e2f575090565b6103ee9150614720565b613e4360236112fb565b7f353536317c43617264733a2063617264206973206e6f7420696e697469616c696020820152621e995960ea1b604082015290565b6103ee613e39565b6103ee613e78565b90613eaa6001610395949361076f613ea260008301610bfb565b611721613e80565b615241565b613eb9601d6112fb565b7f353430327c446562743a206e6f2066756e647320617661696c61626c65000000602082015290565b6103ee613eaf565b6103ee613ee2565b613efc60256112fb565b7f353430337c446562743a20616d6f756e7420697320686967686572207468616e602082015264081919589d60da1b604082015290565b6103ee613ef2565b6103ee613f33565b91907f75aec94f6ad949cfd9728eb818639f1b0b882089b480409ab73245c06469eef490613fee613fc5600080516020616dfe83398151915295613f85611655565b613fc0613fb4613f948a610ca6565b613f9d85614706565b6103ee613faa60006114b1565b8511611721613eea565b835b1115611721613f3b565b6147c7565b948592613fe9908490613fe4613fde836122d187610ca6565b85612157565b615305565b610ca6565b90611864612a7560405190565b61400560296112fb565b7f353331307c4163636f756e743a207472616e73616374696f6e20494420616c726020820152681958591e481d5cd95960ba1b604082015290565b6103ee613ffb565b6103ee614040565b906103959493929161406183614706565b61408a6140746000610cf28560066114be565b61408161202c60006114b1565b14611721614048565b61409e6129676129626103ee846005610bd8565b83156140d4575b6140cf85856140c96140c36122606140bd6009610ca6565b89611ff2565b8761216e565b8461530f565b615477565b6140ed61345860006140e7846005610bd8565b01610c2f565b6140a5565b6140fc601c6112fb565b7f353130327c436f6d6d6f6e3a2061646472657373206973207a65726f00000000602082015290565b6103ee6140f2565b6103ee614125565b610395906141496119c56103e26000611a5f565b141561172161412d565b6103ee91369161097e565b614168602b6112fb565b7f353535307c4f776e657273686970416363657074616e63653a20696e76616c6960208201526a64207369676e617475726560a81b604082015290565b6103ee61415e565b6103ee6141a5565b6000198114611e1f5760010190565b61423d906142346119c5613b9f610395969561422e60006142027f3fc394ec90f88dd7e18c46ffeef55f1b6719a2027f4ae9fbc4ab11827189120090565b01976142286142236142138b610ca6565b61421e611308610d24565b6155ff565b615646565b92614153565b9061566f565b146117216141ad565b61424e61424982610ca6565b6141b5565b90612157565b600080516020616dde8339815191527fc04d7db2ae23be2ba46893b43d7c4af2fc045905461f3df891e1d6c4d0683ef66142d96142cf614293846114d1565b6118c16142ca6002600188019761076f6142ac8a610ca6565b996142c06142ba6000611a5f565b84611a7d565b61424e60006114b1565b615685565b926103b660405190565b0390a2565b906143016142fb6004610395959461076f613ea260008301610bfb565b91615768565b906159b6565b60609061431890615a83565b930190565b905090565b614327601f6112fb565b7f353130317c436f6d6d6f6e3a20696e73756666696369656e742066756e647300602082015290565b6103ee61431d565b6103ee614350565b6143e96143e360007f31f69201fab7912e3ec9850e3ab705964bf46d9d4276bdcbb6d05e965e5f5401949561439487614706565b6143ae6143a26103ee611e24565b885b1115611721614358565b610cf26002600080516020616dde8339815191526143ce88868301611a7d565b6143db8a60018301612157565b019182615adb565b91610726565b926142d9612a7560405190565b61440060386112fb565b7f353431307c44656c617965645769746864726177616c733a207769746864726160208201527f77616c2074696d656c6f636b206e6f7420657870726965640000000000000000604082015290565b6103ee6143f6565b6103ee61444f565b600080516020616dde8339815191527fa578b4c05763eb039caef37b2d7b949c22914c3838384c9c73cefb78c4a8ed0a6142d96142cf61449e846114d1565b6144df6142ca6002600188019761076f6144b78a610ca6565b996144cc6144c66103ee611e24565b8c6143a4565b6142c06144d76138d9565b611721614457565b6118c18582615afa565b6144fd600461076f61039595946005610bd8565b9161453a614534600161452e6145266145218361451b8960066114be565b016114e7565b61332d565b9560066114be565b016114fd565b93614741565b615b37565b610395615b72565b61039561453f565b906103959161455c615b72565b9061039591615d4c565b906103959161454f565b61457a60246112fb565b7f353536307c43617264733a206361726420616c726561647920696e697469616c6020820152631a5e995960e21b604082015290565b6103ee614570565b6103ee6145b0565b9060ff90611a73565b906145d96103ee611a9492151590565b82546145c0565b9069ffffffffffffffff00009060101b611a73565b906146056103ee611a949261234d565b82546145e0565b9061467e60406146766004610395956146646001600160401b03600083016146416146396123fd83610bfb565b6117216145b8565b61464c6001826145c9565b61465f6146598a51151590565b826133ef565b6145f5565b61076f60018201602088015190615db3565b920151615768565b90615de4565b6103ee9081906001600160a01b031681565b906146be61202c611a166146b960006103ee966146b1600090565b50019461071d565b614684565b615e8e565b6146cd601b6112fb565b7f353130307c436f6d6d6f6e3a20616d6f756e74206973207a65726f0000000000602082015290565b6103ee6146c3565b6103ee6146f6565b6103959061471761202c60006114b1565b116117216146fe565b61472981610bfb565b9081614733575090565b6103ee91506123fd90615ed4565b61474d600e8201610ca6565b42106103ee5760070190565b9061476660058301615068565b1561479b571561477c576103ee90600101610ca6565b6103ee90612bc86000600361479360018501610ca6565b930101610ca6565b156147b1576103ee90600101615ef7565b615ef7565b6103ee90612bc86147ac60036143136001850183565b9080821015614318575090565b916147ec906147e284615f18565b6123fd8385615f5a565b6147f4575050565b61039591615f6c565b7ff3a2c404b3362c4238317c3dad1020c2e9e984ba8df38a4a1ff18d57e92a242a90613fee600080516020616dfe833981519152613fe9614841846129a884610ca6565b82612157565b61485160336112fb565b7f353333307c4163636f756e743a2076657269666965642062616c616e6365206e6020820152726f7420657175616c20746f2062616c616e636560681b604082015290565b6103ee614847565b6103ee614896565b906148ba916020612fc26118c160006114d1565b03915afa92831561193357610395936148f89160009161491f575b506148ed6148e36007610ca6565b821461172161489e565b831115611721614358565b614910614909836122d16007610ca6565b6007612157565b61491a60006114d1565b615fbc565b614938915060203d602011611ede57611ed0818361060f565b386148d5565b61494730610726565b7f00000000000000000000000000000000000000000000000000000000000000009061497b6001600160a01b0383166119c5565b1490811561499d575b5061498b57565b60405163703e46dd60e11b8152600490fd5b90506149ad6119c5613b9f616004565b141538614984565b610395906149c46116f8613de3565b614a1e565b6149d360296112fb565b7f353337307c4163636f756e743a20756e617574686f72697a656420696d706c6560208201526836b2b73a30ba34b7b760b91b604082015290565b6103ee6149c9565b6103ee614a0e565b6020614a4891614a316118c160016114d1565b604051938492839182916316da1fb760e11b611e59565b03915afa80156119335761039591600091614a67575b50611721614a16565b614a80915060203d602011612ede57612ed0818361060f565b38614a5e565b610395906149b5565b90614a9c6118c183610726565b906020614aa860405190565b6352d1902d60e01b815292839060049082905afa60009281614b4c575b50614b065750506001614ad55750565b6116e490614ae260405190565b634c9c8ce360e01b8152918291600483016001600160a01b03909116815260200190565b909291614b146103ee612cb9565b8403614b2557610395929350616014565b6116e484614b3260405190565b632a87526960e21b81529182916004830190815260200190565b614b6691935060203d602011611ede57611ed0818361060f565b9138614ac5565b614b7630610726565b614ba86001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000166119c5565b0361498b57565b9261422e9192614c33614c266142236103ee97614bca600090565b507f5066de352c02692985a663f298a4534cb506e7947f703135018b6c3e10f82e0097614c21906125a4614bfd8b610ca6565b91614c18614c09610cb0565b6001600160a01b039096168652565b15156020850152565b61610e565b9461424e61424982610ca6565b614153565b614c42602d6112fb565b7f353331327c4163636f756e743a204f5450207374617465206368616e6765206960208201526c7320696e2070726f677265737360981b604082015290565b6103ee614c38565b6103ee614c81565b614cce61039594614cc9614cab600161076f866005610bd8565b91614cb86123fd84616151565b908115614cd3575b50611721614c89565b616164565b6162a9565b905038614cc0565b634e487b7160e01b600052602260045260246000fd5b9060016002830492168015614d11575b6020831014614d0c57565b614cdb565b91607f1691614d01565b80546000939291614d38614d2e83614cf1565b8085529360200190565b9160018116908115614d8a5750600114614d5157505050565b614d649192939450600052602060002090565b916000925b818410614d765750500190565b805484840152602090930192600101614d69565b92949550505060ff1916825215156020020190565b906103ee91614d1b565b90610395614dc392614dba60405190565b93848092614d9f565b038361060f565b6103ee90614da9565b6103ee6002600080516020616e1e8339815191525b01614dca565b6103ee6003600080516020616e1e833981519152614de8565b6000614e1c91614e15600090565b5001610ca6565b614e2961202c60006114b1565b141590565b614e38602f6112fb565b7f353535317c5472616e73616374696f6e436f6e6669726d6174696f6e3a20696e60208201526e76616c6964207369676e617475726560881b604082015290565b6103ee614e2e565b6103ee614e79565b61423d90614ee86119c5613b9f610395969561422e6000614ec77fa715e0242b72e6c41603cf400167fb9f6392ccc61630d77addc87403bc04180090565b0197614228614223614ed88b610ca6565b614ee3611308610d24565b6163ce565b14611721614e81565b614efb60206112fb565b7f353536327c43617264733a2063617264206973206e6f742064697361626c6564602082015290565b6103ee614ef1565b6103ee614f24565b6103959190614f59614f5182614f4c613ea282610bfb565b615ed4565b611721614f2c565b6134a66001600160401b03826145f5565b614f7460246112fb565b7f353536327c43617264733a206361726420697320616c72656164792064697361602082015263189b195960e21b604082015290565b6103ee614f6a565b6103ee614faa565b60008091614fff61039594614ff9614ff485850192613469614fdb85610c2f565b614feb6001600160401b03612434565b14611721614fb2565b612331565b906145f5565b016133ef565b9061502061202c611a166146b960006103ee966146b1600090565b616435565b61502f601d6112fb565b7f353534317c54696d6572733a2074696d6572206e6f7420616374697665000000602082015290565b6103ee615025565b6103ee615058565b6103ee600061508e92615079600090565b50610cf261508682614e07565b611721615060565b421190565b7fb940258db5193d85cafbc591d0c29fad88d804ff8b9ab71c8f9545af2bb2c07c90613fee600080516020616dfe8339815191526150e56150df6150d683610ca6565b6103ee86614706565b84613fb6565b613fe9614841846122d184610ca6565b6150ff60276112fb565b7f353530317c44656c6179656453657474696e67733a2076616c756520616c726560208201526618591e481cd95d60ca1b604082015290565b6103ee6150f5565b6103ee615138565b906fffffffffffffffffffffffffffffffff90611a73565b6001600160801b03191690565b9061518b615185611a94926001600160801b03191690565b60801c90565b8254615148565b9061ffff60801b9060801b611a73565b610c416103ee6103ee9261ffff1690565b906151c36103ee611a94926151a2565b8254615192565b908082036151d6575050565b61039591906151f8906151f36151ed825460801b90565b8461516d565b610c48565b906151b3565b90610395916151ca565b610395916151f89060209061522e61522882516001600160801b03191690565b8561516d565b015161ffff1690565b9061039591615208565b906001820161528e81615255815460801b90565b61527b61526d61516087516001600160801b03191690565b916001600160801b03191690565b14159081156152d6575b50611721615140565b61529783616151565b156152b657610395936134696002936152af93615237565b9101612157565b61039593613469600293836152d16152af95600089016151fe565b615237565b6152e09150610c48565b6152fd6152f5610c41602087015161ffff1690565b9161ffff1690565b141538615285565b90610395916148a6565b600461076f615322929594956005610bd8565b9261532f6123fd856164f5565b908115615362575b501561534e5761534961039593614741565b61656c565b610395926153499060079061076f85858386565b905038615337565b615374601f6112fb565b7f353331337c4163636f756e743a206e6f2066756e647320746f20626c6f636b00602082015290565b6103ee61536a565b6103ee61539d565b6103ee6080610635565b9067ffffffffffffffff60a01b9060a01b611a73565b906153dd6103ee611a949261234d565b82546153b7565b9060ff60e01b9060e01b611a73565b906154036103ee611a9492151590565b82546153e4565b906154676060600161039594615424614841600087015190565b019261544361543d60208301516001600160a01b031690565b85611a7d565b61546061545a60408301516001600160401b031690565b856153cd565b0151151590565b906153f3565b906103959161540a565b61039594916122d594615488600090565b5015615575576154986001613d47565b84811061552257506155139061550c615518945b6154c36154b960006114b1565b88116117216153a5565b6155036154cf42612331565b6154f36154da6153ad565b976154e38b8a52565b6001600160a01b03166020890152565b6001600160401b03166040870152565b15156060850152565b60066114be565b61546d565b6129a86008610ca6565b9061550c615518946155378461551395980390565b615540846114b1565b7f398a399795162d7b100f484c496bd46d45b5d80499256c51a0a1c0322b32986361556d611a2189610726565b0390a36154ac565b6155139061550c615518949561559561558f6103ee611655565b826143a4565b956154ac565b6155a560226112fb565b7f4f776e657273686970416363657074616e63652875696e74323536206e6f6e63602082015261652960f01b604082015290565b6103ee61559b565b6155e96155d9565b6155fb6155f4825190565b9160200190565b2090565b6155e961563a9161560e600090565b50615622600061561c6155e1565b92015190565b9061562c60405190565b9384926020840192836128d5565b9081038252038261060f565b6103ee906156526165af565b6042916040519161190160f01b8352600283015260228201522090565b6103ee9161567c916165b7565b90929192616652565b6103959060006152af816114b1565b61569c610c52565b906000825260006020830152565b6103ee615694565b6156ba610d24565b9060008252565b6103ee6156b2565b6156d1610d4a565b906000825260208080808086016156e66156aa565b8152016156f16156aa565b8152016156fc6156c1565b8152016000905250565b6103ee6156c9565b615718602e6112fb565b7f353532347c4163636f756e744c696d6974733a207370656e64206c696d69742060208201526d706572696f64206973207a65726f60901b604082015290565b6103ee61570e565b6103ee615758565b615770615706565b506103ee6060820191615854615784845190565b936157a060009561579761202c886114b1565b11611721615760565b61584d6157ad8685015190565b936158466157bc602083015190565b6157ce6157c7610c52565b918a830152565b61580a6157f060406157df8c6114b1565b956157eb876020870152565b015190565b936158036157fc610c52565b958c870152565b6020850152565b61583f615830615818610d24565b9661582c61582660016114b1565b8d8a0152565b5190565b97615839610d4a565b9a8b0152565b6020890152565b6040870152565b6060850152565b6080830152565b818103615866575050565b60016152af8184610cf261587f60006103959801610ca6565b86612157565b906103959161585b565b81810361589a575050565b60006152af816103959401610ca6565b906103959161588f565b8181036158bf575050565b60066152af81846158d861587f60006103959801610ca6565b6158e86001820160018701615885565b6158f86003820160038701615885565b610cf260058201600587016158aa565b90610395916158b4565b60016152af6020610395946157eb61587f600083015190565b9061039591615912565b60006152af8161039594015190565b9061039591615935565b60066152af60806103959461596761587f600083015190565b61597e615975602083015190565b6001870161592b565b61599561598c604083015190565b6003870161592b565b6157eb6159a3606083015190565b60058701615944565b906103959161594e565b906159c0826164f5565b156159db576152af61039593613469600e93600786016159ac565b6152af61039593613469600e93600786016159f98160008901615908565b6159ac565b90615a19615a0d610782845490565b92600052602060002090565b9060005b818110615a2a5750505090565b909192615a4e615a47600192615a3f87610ca6565b815260200190565b9460010190565b929101615a1d565b906103ee916159fe565b90610395614dc392615a7160405190565b93848092615a56565b6103ee90615a60565b60006103ee91615a91606090565b5001615a7a565b615aa260176112fb565b7f353534307c54696d6572733a207a65726f2064656c6179000000000000000000602082015290565b6103ee615a98565b6103ee615acb565b60006152af61039593613469615af0846114b1565b8211611721615ad3565b9061039591615b096007610ca6565b8211615b255761491061490983615b206007610ca6565b611e12565b614910615b3260006114b1565b614909565b929091615b44908461671b565b615b4d57505050565b615b5e906123fd83600186016167b0565b615b66575050565b610395916003016167b0565b615b7d6123fd6167dc565b615b8357565b604051631afcd79f60e31b8152600490fd5b9061039591615ba2615b72565b615cfd565b818110615bb2575050565b80615bc060006001936121a8565b01615ba7565b9190601f8111615bd557505050565b615be761039593600052602060002090565b906020601f840181900483019310615c09575b6020601f909101040190615ba7565b9091508190615bfa565b90615c1c815190565b906001600160401b03821161063057615c3f82615c398554614cf1565b85615bc6565b602090601f8311600114615c7a57611a94929160009183615c6f575b5050600019600883021c1916906002021790565b015190503880615c5b565b601f19831691615c8f85600052602060002090565b9260005b818110615ccd57509160029391856001969410615cb4575b50505002019055565b01516000196008601f8516021c19169055388080615cab565b91936020600181928787015181550195019201615c93565b9061039591615c13565b906121676103ee611a949290565b615d2f61039592615d26615d1c600080516020616e1e83398151915290565b9360028501615ce5565b60038301615ce5565b6001615d3b60006114b1565b91615d468382615cef565b01615cef565b9061039591615b95565b615d6060316112fb565b7f353530307c44656c6179656453657474696e67733a2073657474696e6720616c6020820152701c9958591e481a5b9a5d1a585b1a5e9959607a1b604082015290565b6103ee615d56565b6103ee615da3565b615dda61039592615dd1615dc96123fd85616805565b611721615dab565b60018301615237565b6002429101612157565b615e0361039592615dfa615dc96123fd85616813565b600783016159ac565b600e429101612157565b634e487b7160e01b600052603260045260246000fd5b8054821015615e4657615e3d600191600052602060002090565b91020190600090565b615e0d565b91906121a06103ee611a949390565b90815491680100000000000000008310156106305782615e8291600161039595018155615e23565b90615e4b565b90610be2565b615e9b6123fd8383616821565b15615ecd57615ec891615ec3906001615ebc84615eb88482615e5a565b5490565b9301615e88565b612157565b600190565b5050600090565b6000615ee991615ee2600090565b5001610c2f565b615ef24261346f565b111590565b615f0c6001615f0583610ca6565b9201610ca6565b808210613d9f57900390565b60058101615f2581615068565b615f2d575050565b615f54600683615f48615f436001610395970190565b61683a565b610cf26003820161683a565b90615adb565b610395919060010161684a565b61684a565b610395919060030161684a565b615f92615f8c6103ee9263ffffffff1690565b60e01b90565b6001600160e01b03191690565b6001600160a01b0390911681526040810192916103959160200152565b615fff600492615ff061039595615fd663a9059cbb615f79565b92615fe060405190565b9687946020860190815201615f9f565b6020820181038252038361060f565b61685f565b6103ee60006122856103ee612cb9565b9061601e826168e7565b61602782610726565b7fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b61605160405190565b600090a2805161606461202c60006114b1565b1115616073576118ff91616955565b5050610395616912565b61608760406112fb565b7f43617264436f6e6669726d6174696f6e2861646472657373206361726441646460208201527f726573732c626f6f6c2069734f776e65722c75696e74323536206e6f6e636529604082015290565b6103ee61607d565b6155e96160d6565b6128e861039594616105606094989795611560608086019a6000870152565b15156040830152565b6155e96161196160de565b82516001600160a01b03169061563a616140604061613a6020880151151590565b96015190565b6040519586946020860194856160e6565b600261615f91614e15600090565b421090565b61617060028201610ca6565b42106103ee5760010190565b61618660296112fb565b7f353531307c4f6e6554696d6550617373776f72643a20696e76616c6964204f54602082015268281031b7bab73a32b960b91b604082015290565b6103ee61617c565b6103ee6161c1565b60016103ee910161ffff1690565b610391906001600160a01b031660601b90565b6103919061ffff1660f01b90565b6010939261622160148361621960029561076f976161df565b0180926161f2565b0180926001600160801b0319169052565b61076f61624a92602092616244815190565b94859290565b93849101611025565b6103ee91616232565b61626660216112fb565b7f353531317c4f6e6554696d6550617373776f72643a20696e76616c6964204f546020820152600560fc1b604082015290565b6103ee61625c565b6103ee616299565b9391929091906162b885610c48565b946162cf61ffff871661ffff8416106117216161c9565b8482955b61ffff88165b61ffff8816101561634457600061631660209261630a8a8a61563a6162fd60405190565b9485938985019384616200565b60405191829182616253565b039060025afa15611933576162d961633c61633661516060005160001b90565b976161d1565b9690506162d3565b6163719192949750610395965061637793955061636861526d615160875460801b90565b146117216162a1565b8261516d565b6151b3565b61638660266112fb565b7f5472616e73616374696f6e436f6e6669726d6174696f6e2875696e74323536206020820152656e6f6e63652960d01b604082015290565b6103ee61637c565b6155e96163be565b6155e961563a916163dd600090565b50615622600061561c6163c6565b634e487b7160e01b600052603160045260246000fd5b61039591600091615e4b565b8054801561643057600019019061642d6164278383615e23565b90616401565b55565b6163eb565b90616446613fe98260018501615e88565b61645060006114b1565b81146164ed576164af6103ee926000926164a4956164a9600197889361647e616478866114b1565b82611e12565b8885019161649661648d845490565b611ea5896114b1565b8083036164b4575b50505090565b61640d565b01615e88565b6121a8565b615e826164dd6164e5946164d46164ce615ec39589615e23565b906104a9565b92839188615e23565b888801615e88565b38808061649e565b505050600090565b600e61615f91614e15600090565b61650d603b6112fb565b7f353532307c4163636f756e744c696d6974733a20616d6f756e7420657863656560208201527f64732073696e676c65207472616e73616374696f6e206c696d69740000000000604082015290565b6103ee616503565b6103ee61655c565b9161659e9061658b6165806103ee86610ca6565b841115611721616564565b61659484615f18565b6123fd83856169d0565b6165a6575050565b61039591616a4d565b6103ee616a9c565b906000916165c3825190565b6165d061202c60416114b1565b036165fa576165f392506020820151906060604084015193015160001a90616b19565b9192909190565b509050616612611a1661660d6000611a5f565b925190565b909160029190565b634e487b7160e01b600052602160045260246000fd5b6004111561663a57565b61661a565b9061039582616630565b6103ee906114b1565b61665c600061663f565b6166658261663f565b0361666e575050565b616678600161663f565b6166818261663f565b036166985760405163f645eedf60e01b8152600490fd5b6166a2600261663f565b6166ab8261663f565b036166d9576116e46166bc83616649565b60405163fce698f760e01b81529182916004830190815260200190565b6166ec6166e6600361663f565b9161663f565b146166f45750565b6116e49061670160405190565b6335e2f38360e21b81529182916004830190815260200190565b600581019061672982615068565b1561673657505050600090565b6103ee61202c91611ea56006615f0560006167519701610ca6565b101590565b616760602e6112fb565b7f353533317c5370656e644c696d6974733a20696e73756666696369656e74207360208201526d1c195b9d081d1bc818d85b98d95b60921b604082015290565b6103ee616756565b6103ee6167a0565b9061424e60016103959301916167d36167c884610ca6565b8211156117216167a8565b6122d183610ca6565b6103ee7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00612314565b6002614e1c91614e15600090565b600e614e1c91614e15600090565b614e1c916001613fe992616833600090565b5001615e88565b6103959060016152af60006114b1565b9061424e60016103959301916129a883610ca6565b61686b61687291610726565b9182616be0565b805161688161202c60006114b1565b141590816168c3575b506168925750565b6116e49061689f60405190565b635274afe760e01b8152918291600483016001600160a01b03909116815260200190565b6168e191508060206168d66123fd935190565b818301019101612d25565b3861688a565b803b6168f661202c60006114b1565b14614ad55761039590600061690c6103ee612cb9565b01611a7d565b61691c60006114b1565b341161692457565b60405163b398979f60e01b8152600490fd5b3d15616950576169453d6112fb565b903d6000602084013e565b606090565b6000806103ee93616964606090565b50602081519101845af4616976616936565b91616bf4565b61698660286112fb565b7f353532317c4163636f756e744c696d6974733a207370656e64206c696d697420602082015267195e18d95959195960c21b604082015290565b6103ee61697c565b6103ee6169c0565b61039591906169ed6169e58360018401616c64565b6117216169c8565b615f5a565b6169fc602f6112fb565b7f353532337c4163636f756e744c696d6974733a206e6f206f7470207370656e6460208201526e081b1a5b5a5d08195e18d959591959608a1b604082015290565b6103ee6169f2565b6103ee616a3d565b6103959190600301615f67616a628383616c64565b611721616a45565b909594926103959461069c611ab392616a95608096616a8e60a088019c6000890152565b6020870152565b6040850152565b7f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6155e9616ac8616c7c565b616ad0616d00565b9061563a616add30610726565b6040519586946020860194469286616a6a565b6128e8610395946125a4606094989795616b0f608086019a6000870152565b60ff166020850152565b9091616b2484616649565b616b5061202c7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a06114b1565b11616bcc5790616b7260209460009493616b6960405190565b94859485616af0565b838052039060015afa1561193357600051600091616b8f83611a5f565b6001600160a01b0381166001600160a01b03841614616bb85750616bb2836114b1565b91929190565b915091616bc4906114b1565b909160019190565b505050616bd96000611a5f565b9160039190565b6103ee91616bee60006114b1565b91616d4f565b90616bff5750616dae565b8151616c0e61202c60006114b1565b1480616c4e575b616c1d575090565b6116e490616c2a60405190565b639996b31560e01b8152918291600483016001600160a01b03909116815260200190565b50803b616c5e61202c60006114b1565b14616c15565b61202c6103ee615ef292616c76600090565b50615ef7565b600080516020616e1e833981519152616c966103ee614dd3565b90616c9f825190565b616cac61202c60006114b1565b1115616cbe57506155fb6155f4825190565b616cc89150610ca6565b616cd260006114b1565b8114616cdb5790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b600080516020616e1e833981519152616d1a6103ee614dee565b90616d23825190565b616d3061202c60006114b1565b1115616d4257506155fb6155f4825190565b616cc89150600101610ca6565b91616d5930610726565b81813110616d7d5750600082819260206103ee969551920190855af1616976616936565b6116e490616d8a60405190565b63cd78605960e01b8152918291600483016001600160a01b03909116815260200190565b8051616dbd61202c60006114b1565b1115616dcb57805190602001fd5b604051630a12f52160e11b8152600490fdfe76d8021c4b979e99674b80f4e0cc10154af0e5f32df05eb51b29a464e24355003486e95df892f06bd37fd38c44e2fee4c4efb6660a66e6eb20ed6d8a167eae00a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100a2646970667358221220299c9946e80716a606163fc8482c2e4aa42277a57c723d628ffa5e98c614887064736f6c63430008160033"; + + private static String librariesLinkedBinary; public static final String FUNC_UPGRADE_INTERFACE_VERSION = "UPGRADE_INTERFACE_VERSION"; + public static final String FUNC_ACTIVECARDADDRESSES = "activeCardAddresses"; + + public static final String FUNC_ADDCARD = "addCard"; + + public static final String FUNC_AUTHLIMITMARGIN = "authLimitMargin"; + public static final String FUNC_AUTHORIZEDTRANSACTIONS = "authorizedTransactions"; public static final String FUNC_AVAILABLEFORDEBTPAYMENT = "availableForDebtPayment"; @@ -56,12 +76,18 @@ class TangemPaymentAccount extends Contract { public static final String FUNC_CANCELWITHDRAWAL = "cancelWithdrawal"; - public static final String FUNC_CARDWITHOTP = "cardWithOtp"; + public static final String FUNC_CARDCONFIRMATIONNONCE = "cardConfirmationNonce"; + + public static final String FUNC_CARDS = "cards"; public static final String FUNC_DEBTAMOUNT = "debtAmount"; + public static final String FUNC_DISABLECARD = "disableCard"; + public static final String FUNC_EIP712DOMAIN = "eip712Domain"; + public static final String FUNC_ENABLECARD = "enableCard"; + public static final String FUNC_FACTORY = "factory"; public static final String FUNC_INCREASEVERIFIEDBALANCE = "increaseVerifiedBalance"; @@ -76,32 +102,30 @@ class TangemPaymentAccount extends Contract { public static final String FUNC_ISWITHDRAWALREADY = "isWithdrawalReady"; - public static final String FUNC_LIMITS = "limits"; - public static final String FUNC_OWNER = "owner"; public static final String FUNC_OWNERSHIPACCEPTANCENONCE = "ownershipAcceptanceNonce"; + public static final String FUNC_PAYDEBT = "payDebt"; + public static final String FUNC_PAYMENTTOKEN = "paymentToken"; - public static final String FUNC_PENDINGREFUNDAMOUNT = "pendingRefundAmount"; - - public static final String FUNC_PENDINGREFUNDTOTAL = "pendingRefundTotal"; - - public static final String FUNC_PROCESSAUTHORIZATION = "processAuthorization"; - public static final String FUNC_PROCESSAUTHORIZATIONCHANGE = "processAuthorizationChange"; - public static final String FUNC_PROCESSAUTHORIZATIONNOOTP = "processAuthorizationNoOtp"; - public static final String FUNC_PROCESSDEBT = "processDebt"; - public static final String FUNC_PROCESSPENDINGREFUNDPAYMENT = "processPendingRefundPayment"; + public static final String FUNC_PROCESSNOCONFIRMATIONAUTHORIZATION = "processNoConfirmationAuthorization"; + + public static final String FUNC_PROCESSOTPAUTHORIZATION = "processOtpAuthorization"; public static final String FUNC_PROCESSREFUNDPAYMENT = "processRefundPayment"; public static final String FUNC_PROCESSSETTLEMENT = "processSettlement"; + public static final String FUNC_PROCESSSIGNATUREAUTHORIZATION = "processSignatureAuthorization"; + + public static final String FUNC_PROCESSUNSETTLEDTRANSACTION = "processUnsettledTransaction"; + public static final String FUNC_PROCESSWITHDRAWAL = "processWithdrawal"; public static final String FUNC_PROCESSOR = "processor"; @@ -110,11 +134,13 @@ class TangemPaymentAccount extends Contract { public static final String FUNC_REGISTRY = "registry"; - public static final String FUNC_SAVEPENDINGREFUND = "savePendingRefund"; + public static final String FUNC_REMOVEOWNER = "removeOwner"; public static final String FUNC_SECURITYDELAY = "securityDelay"; - public static final String FUNC_SETCARD = "setCard"; + public static final String FUNC_SETAUTHLIMITMARGIN = "setAuthLimitMargin"; + + public static final String FUNC_SETCARDISOWNER = "setCardIsOwner"; public static final String FUNC_SETLIMITS = "setLimits"; @@ -122,20 +148,18 @@ class TangemPaymentAccount extends Contract { public static final String FUNC_SETOWNER = "setOwner"; - public static final String FUNC_SETPROCESSOR = "setProcessor"; - public static final String FUNC_SETVERIFIEDBALANCE = "setVerifiedBalance"; - public static final String FUNC_SETTLEMENTPERIOD = "settlementPeriod"; + public static final String FUNC_TRANSACTIONCONFIRMATIONNONCE = "transactionConfirmationNonce"; public static final String FUNC_TRUSTEDFORWARDER = "trustedForwarder"; - public static final String FUNC_UNBLOCKUNSETTLEDTRANSACTION = "unblockUnsettledTransaction"; - public static final String FUNC_UPGRADETOANDCALL = "upgradeToAndCall"; public static final String FUNC_VERIFIEDBALANCE = "verifiedBalance"; + public static final String FUNC_WITHDRAWALADDRESS = "withdrawalAddress"; + public static final String FUNC_WITHDRAWALAMOUNT = "withdrawalAmount"; public static final String FUNC_WITHDRAWALREADYTIMESTAMP = "withdrawalReadyTimestamp"; @@ -143,240 +167,319 @@ class TangemPaymentAccount extends Contract { public static final String FUNC_WRITEOFFDEBT = "writeOffDebt"; public static final Event ACCOUNTSTATEAFTERSETTLEMENT_EVENT = new Event("AccountStateAfterSettlement", - Arrays.asList(new TypeReference() { - }, new TypeReference() { - }, new TypeReference() { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference() {}, new TypeReference() {}, new TypeReference() {})); + ; - public static final Event CARDSET_EVENT = new Event("CardSet", - Arrays.asList(new TypeReference

() { - }, new TypeReference() { - }, new TypeReference() { - })); + public static final Event AUTHLIMITMARGINSET_EVENT = new Event("AuthLimitMarginSet", + Arrays.>asList(new TypeReference() {})); + ; + + public static final Event CARDADDED_EVENT = new Event("CardAdded", + Arrays.>asList(new TypeReference
() {}, new TypeReference() {})); + ; + + public static final Event CARDDISABLED_EVENT = new Event("CardDisabled", + Arrays.>asList(new TypeReference
() {})); + ; + + public static final Event CARDENABLED_EVENT = new Event("CardEnabled", + Arrays.>asList(new TypeReference
() {}, new TypeReference() {})); + ; public static final Event DEBTINCREASED_EVENT = new Event("DebtIncreased", - Arrays.asList(new TypeReference() { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference() {}, new TypeReference() {})); + ; public static final Event DEBTPAID_EVENT = new Event("DebtPaid", - Arrays.asList(new TypeReference() { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference() {}, new TypeReference() {})); + ; public static final Event DEBTWRITTENOFF_EVENT = new Event("DebtWrittenOff", - Arrays.asList(new TypeReference() { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference() {}, new TypeReference() {})); + ; public static final Event EIP712DOMAINCHANGED_EVENT = new Event("EIP712DomainChanged", - List.of()); + Arrays.>asList()); + ; public static final Event INITIALIZED_EVENT = new Event("Initialized", - List.of(new TypeReference() { - })); + Arrays.>asList(new TypeReference() {})); + ; public static final Event INSUFFICIENTFUNDSONFORCEDAUTH_EVENT = new Event("InsufficientFundsOnForcedAuth", - Arrays.asList(new TypeReference(true) { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference(true) {}, new TypeReference
(true) {}, new TypeReference() {})); + ; public static final Event LIMITSSET_EVENT = new Event("LimitsSet", - Arrays.asList(new TypeReference() { - }, new TypeReference() { - }, new TypeReference() { - }, new TypeReference() { - })); - - public static final Event NOOTPTRANSACTIONAUTHORIZED_EVENT = new Event("NoOtpTransactionAuthorized", - Arrays.asList(new TypeReference(true) { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference
() {}, new TypeReference() {})); + ; public static final Event OTPSTATESET_EVENT = new Event("OtpStateSet", - Arrays.asList(new TypeReference() { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference
() {}, new TypeReference() {})); + ; + + public static final Event OWNERREMOVED_EVENT = new Event("OwnerRemoved", + Arrays.>asList(new TypeReference
() {})); + ; public static final Event OWNERSET_EVENT = new Event("OwnerSet", - List.of(new TypeReference
() { - })); - - public static final Event PENDINGREFUNDSAVED_EVENT = new Event("PendingRefundSaved", - Arrays.asList(new TypeReference(true) { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference
() {})); + ; public static final Event PROCESSORSET_EVENT = new Event("ProcessorSet", - Arrays.asList(new TypeReference
() { - }, new TypeReference
() { - })); + Arrays.>asList(new TypeReference
() {}, new TypeReference
() {})); + ; public static final Event REFUNDPAID_EVENT = new Event("RefundPaid", - Arrays.asList(new TypeReference(true) { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference(true) {}, new TypeReference
(true) {}, new TypeReference() {})); + ; public static final Event TRANSACTIONAMOUNTCHANGED_EVENT = new Event("TransactionAmountChanged", - Arrays.asList(new TypeReference(true) { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference(true) {}, new TypeReference
(true) {}, new TypeReference() {})); + ; - public static final Event TRANSACTIONAUTHORIZED_EVENT = new Event("TransactionAuthorized", - Arrays.asList(new TypeReference(true) { - }, new TypeReference() { - }, new TypeReference() { - }, new TypeReference() { - })); + public static final Event TRANSACTIONAUTHORIZEDNOCONFIRMATION_EVENT = new Event("TransactionAuthorizedNoConfirmation", + Arrays.>asList(new TypeReference(true) {}, new TypeReference
(true) {}, new TypeReference() {})); + ; + + public static final Event TRANSACTIONAUTHORIZEDWITHOTP_EVENT = new Event("TransactionAuthorizedWithOtp", + Arrays.>asList(new TypeReference(true) {}, new TypeReference
(true) {}, new TypeReference() {}, new TypeReference() {}, new TypeReference() {})); + ; + + public static final Event TRANSACTIONAUTHORIZEDWITHSIGNATURE_EVENT = new Event("TransactionAuthorizedWithSignature", + Arrays.>asList(new TypeReference(true) {}, new TypeReference
(true) {}, new TypeReference() {}, new TypeReference() {})); + ; public static final Event TRANSACTIONSETTLED_EVENT = new Event("TransactionSettled", - Arrays.asList(new TypeReference(true) { - }, new TypeReference() { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference(true) {}, new TypeReference
(true) {}, new TypeReference() {}, new TypeReference() {})); + ; public static final Event UNSETTLEDTRANSACTIONUNBLOCKED_EVENT = new Event("UnsettledTransactionUnblocked", - Arrays.asList(new TypeReference(true) { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference(true) {}, new TypeReference
(true) {}, new TypeReference() {})); + ; public static final Event UPGRADED_EVENT = new Event("Upgraded", - List.of(new TypeReference
(true) { - })); + Arrays.>asList(new TypeReference
(true) {})); + ; public static final Event VERIFIEDBALANCEINCREASED_EVENT = new Event("VerifiedBalanceIncreased", - Arrays.asList(new TypeReference() { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference() {}, new TypeReference() {})); + ; public static final Event VERIFIEDBALANCESET_EVENT = new Event("VerifiedBalanceSet", - List.of(new TypeReference() { - })); + Arrays.>asList(new TypeReference() {})); + ; public static final Event WITHDRAWALCANCELED_EVENT = new Event("WithdrawalCanceled", - List.of()); + Arrays.>asList(new TypeReference
(true) {}, new TypeReference() {})); + ; public static final Event WITHDRAWALCOMPLETE_EVENT = new Event("WithdrawalComplete", - Arrays.asList(new TypeReference
(true) { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference
(true) {}, new TypeReference() {})); + ; public static final Event WITHDRAWALINITIATED_EVENT = new Event("WithdrawalInitiated", - Arrays.asList(new TypeReference() { - }, new TypeReference() { - })); + Arrays.>asList(new TypeReference
(true) {}, new TypeReference() {}, new TypeReference() {})); + ; @Deprecated - protected TangemPaymentAccount(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + protected TangemPaymentAccount(String contractAddress, Web3j web3j, Credentials credentials, + BigInteger gasPrice, BigInteger gasLimit) { super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); } - protected TangemPaymentAccount(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + protected TangemPaymentAccount(String contractAddress, Web3j web3j, Credentials credentials, + ContractGasProvider contractGasProvider) { super(BINARY, contractAddress, web3j, credentials, contractGasProvider); } @Deprecated - protected TangemPaymentAccount(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + protected TangemPaymentAccount(String contractAddress, Web3j web3j, + TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); } - protected TangemPaymentAccount(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + protected TangemPaymentAccount(String contractAddress, Web3j web3j, + TransactionManager transactionManager, ContractGasProvider contractGasProvider) { super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); } - public static List getAccountStateAfterSettlementEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(ACCOUNTSTATEAFTERSETTLEMENT_EVENT, transactionReceipt); + public static List getAccountStateAfterSettlementEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(ACCOUNTSTATEAFTERSETTLEMENT_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { AccountStateAfterSettlementEventResponse typedResponse = new AccountStateAfterSettlementEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.balance = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); typedResponse.blockedAmount = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); typedResponse.debtTotal = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); - typedResponse.pendingRefundTotal = (BigInteger) eventValues.getNonIndexedValues().get(3).getValue(); responses.add(typedResponse); } return responses; } - public static AccountStateAfterSettlementEventResponse getAccountStateAfterSettlementEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(ACCOUNTSTATEAFTERSETTLEMENT_EVENT, log); + public static AccountStateAfterSettlementEventResponse getAccountStateAfterSettlementEventFromLog( + Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(ACCOUNTSTATEAFTERSETTLEMENT_EVENT, log); AccountStateAfterSettlementEventResponse typedResponse = new AccountStateAfterSettlementEventResponse(); typedResponse.log = log; typedResponse.balance = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); typedResponse.blockedAmount = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); typedResponse.debtTotal = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); - typedResponse.pendingRefundTotal = (BigInteger) eventValues.getNonIndexedValues().get(3).getValue(); return typedResponse; } - public static CardSetEventResponse getCardSetEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(CARDSET_EVENT, log); - CardSetEventResponse typedResponse = new CardSetEventResponse(); + public Flowable accountStateAfterSettlementEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getAccountStateAfterSettlementEventFromLog(log)); + } + + public Flowable accountStateAfterSettlementEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(ACCOUNTSTATEAFTERSETTLEMENT_EVENT)); + return accountStateAfterSettlementEventFlowable(filter); + } + + public static List getAuthLimitMarginSetEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(AUTHLIMITMARGINSET_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + AuthLimitMarginSetEventResponse typedResponse = new AuthLimitMarginSetEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.authLimitMargin = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static AuthLimitMarginSetEventResponse getAuthLimitMarginSetEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(AUTHLIMITMARGINSET_EVENT, log); + AuthLimitMarginSetEventResponse typedResponse = new AuthLimitMarginSetEventResponse(); typedResponse.log = log; - typedResponse.card = (String) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.otpRoot = (byte[]) eventValues.getNonIndexedValues().get(1).getValue(); - typedResponse.otpCounter = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); + typedResponse.authLimitMargin = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); return typedResponse; } - public static DebtIncreasedEventResponse getDebtIncreasedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(DEBTINCREASED_EVENT, log); - DebtIncreasedEventResponse typedResponse = new DebtIncreasedEventResponse(); + public Flowable authLimitMarginSetEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getAuthLimitMarginSetEventFromLog(log)); + } + + public Flowable authLimitMarginSetEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(AUTHLIMITMARGINSET_EVENT)); + return authLimitMarginSetEventFlowable(filter); + } + + public static List getCardAddedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(CARDADDED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + CardAddedEventResponse typedResponse = new CardAddedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.cardAddress = (String) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.cardData = (CardParams) eventValues.getNonIndexedValues().get(1); + responses.add(typedResponse); + } + return responses; + } + + public static CardAddedEventResponse getCardAddedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(CARDADDED_EVENT, log); + CardAddedEventResponse typedResponse = new CardAddedEventResponse(); typedResponse.log = log; - typedResponse.increase = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.debtAmount = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + typedResponse.cardAddress = (String) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.cardData = (CardParams) eventValues.getNonIndexedValues().get(1); return typedResponse; } - public static List getCardSetEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(CARDSET_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - CardSetEventResponse typedResponse = new CardSetEventResponse(); + public Flowable cardAddedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getCardAddedEventFromLog(log)); + } + + public Flowable cardAddedEventFlowable(DefaultBlockParameter startBlock, + DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(CARDADDED_EVENT)); + return cardAddedEventFlowable(filter); + } + + public static List getCardDisabledEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(CARDDISABLED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + CardDisabledEventResponse typedResponse = new CardDisabledEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.card = (String) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.otpRoot = (byte[]) eventValues.getNonIndexedValues().get(1).getValue(); - typedResponse.otpCounter = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); responses.add(typedResponse); } return responses; } - public static DebtPaidEventResponse getDebtPaidEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(DEBTPAID_EVENT, log); - DebtPaidEventResponse typedResponse = new DebtPaidEventResponse(); + public static CardDisabledEventResponse getCardDisabledEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(CARDDISABLED_EVENT, log); + CardDisabledEventResponse typedResponse = new CardDisabledEventResponse(); typedResponse.log = log; - typedResponse.paid = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.debtLeft = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + typedResponse.card = (String) eventValues.getNonIndexedValues().get(0).getValue(); return typedResponse; } - public static DebtWrittenOffEventResponse getDebtWrittenOffEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(DEBTWRITTENOFF_EVENT, log); - DebtWrittenOffEventResponse typedResponse = new DebtWrittenOffEventResponse(); - typedResponse.log = log; - typedResponse.writtenOff = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.debtLeft = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); - return typedResponse; + public Flowable cardDisabledEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getCardDisabledEventFromLog(log)); } - public static List getEIP712DomainChangedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(EIP712DOMAINCHANGED_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - EIP712DomainChangedEventResponse typedResponse = new EIP712DomainChangedEventResponse(); + public Flowable cardDisabledEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(CARDDISABLED_EVENT)); + return cardDisabledEventFlowable(filter); + } + + public static List getCardEnabledEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(CARDENABLED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + CardEnabledEventResponse typedResponse = new CardEnabledEventResponse(); typedResponse.log = eventValues.getLog(); + typedResponse.card = (String) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.isOwner = (Boolean) eventValues.getNonIndexedValues().get(1).getValue(); responses.add(typedResponse); } return responses; } - public static List getDebtIncreasedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(DEBTINCREASED_EVENT, transactionReceipt); + public static CardEnabledEventResponse getCardEnabledEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(CARDENABLED_EVENT, log); + CardEnabledEventResponse typedResponse = new CardEnabledEventResponse(); + typedResponse.log = log; + typedResponse.card = (String) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.isOwner = (Boolean) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; + } + + public Flowable cardEnabledEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getCardEnabledEventFromLog(log)); + } + + public Flowable cardEnabledEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(CARDENABLED_EVENT)); + return cardEnabledEventFlowable(filter); + } + + public static List getDebtIncreasedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(DEBTINCREASED_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { DebtIncreasedEventResponse typedResponse = new DebtIncreasedEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.increase = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); @@ -386,34 +489,31 @@ class TangemPaymentAccount extends Contract { return responses; } - public static EIP712DomainChangedEventResponse getEIP712DomainChangedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(EIP712DOMAINCHANGED_EVENT, log); - EIP712DomainChangedEventResponse typedResponse = new EIP712DomainChangedEventResponse(); + public static DebtIncreasedEventResponse getDebtIncreasedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(DEBTINCREASED_EVENT, log); + DebtIncreasedEventResponse typedResponse = new DebtIncreasedEventResponse(); typedResponse.log = log; + typedResponse.increase = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.debtAmount = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); return typedResponse; } - public static InitializedEventResponse getInitializedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(INITIALIZED_EVENT, log); - InitializedEventResponse typedResponse = new InitializedEventResponse(); - typedResponse.log = log; - typedResponse.version = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; + public Flowable debtIncreasedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getDebtIncreasedEventFromLog(log)); } - public static InsufficientFundsOnForcedAuthEventResponse getInsufficientFundsOnForcedAuthEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(INSUFFICIENTFUNDSONFORCEDAUTH_EVENT, log); - InsufficientFundsOnForcedAuthEventResponse typedResponse = new InsufficientFundsOnForcedAuthEventResponse(); - typedResponse.log = log; - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.insufficientAmount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; + public Flowable debtIncreasedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(DEBTINCREASED_EVENT)); + return debtIncreasedEventFlowable(filter); } - public static List getDebtPaidEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(DEBTPAID_EVENT, transactionReceipt); + public static List getDebtPaidEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(DEBTPAID_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { DebtPaidEventResponse typedResponse = new DebtPaidEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.paid = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); @@ -423,39 +523,31 @@ class TangemPaymentAccount extends Contract { return responses; } - public static LimitsSetEventResponse getLimitsSetEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(LIMITSSET_EVENT, log); - LimitsSetEventResponse typedResponse = new LimitsSetEventResponse(); + public static DebtPaidEventResponse getDebtPaidEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(DEBTPAID_EVENT, log); + DebtPaidEventResponse typedResponse = new DebtPaidEventResponse(); typedResponse.log = log; - typedResponse.singleTransactionLimit = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.spendLimit = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); - typedResponse.noOtpSpendLimit = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); - typedResponse.spendLimitsPeriod = (BigInteger) eventValues.getNonIndexedValues().get(3).getValue(); + typedResponse.paid = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.debtLeft = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); return typedResponse; } - public static NoOtpTransactionAuthorizedEventResponse getNoOtpTransactionAuthorizedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(NOOTPTRANSACTIONAUTHORIZED_EVENT, log); - NoOtpTransactionAuthorizedEventResponse typedResponse = new NoOtpTransactionAuthorizedEventResponse(); - typedResponse.log = log; - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; + public Flowable debtPaidEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getDebtPaidEventFromLog(log)); } - public static OtpStateSetEventResponse getOtpStateSetEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(OTPSTATESET_EVENT, log); - OtpStateSetEventResponse typedResponse = new OtpStateSetEventResponse(); - typedResponse.log = log; - typedResponse.otpRoot = (byte[]) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.otpCounter = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); - return typedResponse; + public Flowable debtPaidEventFlowable(DefaultBlockParameter startBlock, + DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(DEBTPAID_EVENT)); + return debtPaidEventFlowable(filter); } - public static List getDebtWrittenOffEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(DEBTWRITTENOFF_EVENT, transactionReceipt); + public static List getDebtWrittenOffEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(DEBTWRITTENOFF_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { DebtWrittenOffEventResponse typedResponse = new DebtWrittenOffEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.writtenOff = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); @@ -465,10 +557,232 @@ class TangemPaymentAccount extends Contract { return responses; } - public static List getOwnerSetEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(OWNERSET_EVENT, transactionReceipt); + public static DebtWrittenOffEventResponse getDebtWrittenOffEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(DEBTWRITTENOFF_EVENT, log); + DebtWrittenOffEventResponse typedResponse = new DebtWrittenOffEventResponse(); + typedResponse.log = log; + typedResponse.writtenOff = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.debtLeft = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; + } + + public Flowable debtWrittenOffEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getDebtWrittenOffEventFromLog(log)); + } + + public Flowable debtWrittenOffEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(DEBTWRITTENOFF_EVENT)); + return debtWrittenOffEventFlowable(filter); + } + + public static List getEIP712DomainChangedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(EIP712DOMAINCHANGED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + EIP712DomainChangedEventResponse typedResponse = new EIP712DomainChangedEventResponse(); + typedResponse.log = eventValues.getLog(); + responses.add(typedResponse); + } + return responses; + } + + public static EIP712DomainChangedEventResponse getEIP712DomainChangedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(EIP712DOMAINCHANGED_EVENT, log); + EIP712DomainChangedEventResponse typedResponse = new EIP712DomainChangedEventResponse(); + typedResponse.log = log; + return typedResponse; + } + + public Flowable eIP712DomainChangedEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getEIP712DomainChangedEventFromLog(log)); + } + + public Flowable eIP712DomainChangedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(EIP712DOMAINCHANGED_EVENT)); + return eIP712DomainChangedEventFlowable(filter); + } + + public static List getInitializedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(INITIALIZED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + InitializedEventResponse typedResponse = new InitializedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.version = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static InitializedEventResponse getInitializedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(INITIALIZED_EVENT, log); + InitializedEventResponse typedResponse = new InitializedEventResponse(); + typedResponse.log = log; + typedResponse.version = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable initializedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getInitializedEventFromLog(log)); + } + + public Flowable initializedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(INITIALIZED_EVENT)); + return initializedEventFlowable(filter); + } + + public static List getInsufficientFundsOnForcedAuthEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(INSUFFICIENTFUNDSONFORCEDAUTH_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + InsufficientFundsOnForcedAuthEventResponse typedResponse = new InsufficientFundsOnForcedAuthEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.insufficientAmount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static InsufficientFundsOnForcedAuthEventResponse getInsufficientFundsOnForcedAuthEventFromLog( + Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(INSUFFICIENTFUNDSONFORCEDAUTH_EVENT, log); + InsufficientFundsOnForcedAuthEventResponse typedResponse = new InsufficientFundsOnForcedAuthEventResponse(); + typedResponse.log = log; + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.insufficientAmount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable insufficientFundsOnForcedAuthEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getInsufficientFundsOnForcedAuthEventFromLog(log)); + } + + public Flowable insufficientFundsOnForcedAuthEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(INSUFFICIENTFUNDSONFORCEDAUTH_EVENT)); + return insufficientFundsOnForcedAuthEventFlowable(filter); + } + + public static List getLimitsSetEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(LIMITSSET_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + LimitsSetEventResponse typedResponse = new LimitsSetEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.card = (String) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.limitsParams = (LimitsParams) eventValues.getNonIndexedValues().get(1); + responses.add(typedResponse); + } + return responses; + } + + public static LimitsSetEventResponse getLimitsSetEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(LIMITSSET_EVENT, log); + LimitsSetEventResponse typedResponse = new LimitsSetEventResponse(); + typedResponse.log = log; + typedResponse.card = (String) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.limitsParams = (LimitsParams) eventValues.getNonIndexedValues().get(1); + return typedResponse; + } + + public Flowable limitsSetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getLimitsSetEventFromLog(log)); + } + + public Flowable limitsSetEventFlowable(DefaultBlockParameter startBlock, + DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(LIMITSSET_EVENT)); + return limitsSetEventFlowable(filter); + } + + public static List getOtpStateSetEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(OTPSTATESET_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + OtpStateSetEventResponse typedResponse = new OtpStateSetEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.card = (String) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.otpState = (OtpState) eventValues.getNonIndexedValues().get(1); + responses.add(typedResponse); + } + return responses; + } + + public static OtpStateSetEventResponse getOtpStateSetEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(OTPSTATESET_EVENT, log); + OtpStateSetEventResponse typedResponse = new OtpStateSetEventResponse(); + typedResponse.log = log; + typedResponse.card = (String) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.otpState = (OtpState) eventValues.getNonIndexedValues().get(1); + return typedResponse; + } + + public Flowable otpStateSetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getOtpStateSetEventFromLog(log)); + } + + public Flowable otpStateSetEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(OTPSTATESET_EVENT)); + return otpStateSetEventFlowable(filter); + } + + public static List getOwnerRemovedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(OWNERREMOVED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + OwnerRemovedEventResponse typedResponse = new OwnerRemovedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.previousOwner = (String) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static OwnerRemovedEventResponse getOwnerRemovedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(OWNERREMOVED_EVENT, log); + OwnerRemovedEventResponse typedResponse = new OwnerRemovedEventResponse(); + typedResponse.log = log; + typedResponse.previousOwner = (String) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable ownerRemovedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getOwnerRemovedEventFromLog(log)); + } + + public Flowable ownerRemovedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(OWNERREMOVED_EVENT)); + return ownerRemovedEventFlowable(filter); + } + + public static List getOwnerSetEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(OWNERSET_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { OwnerSetEventResponse typedResponse = new OwnerSetEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.owner = (String) eventValues.getNonIndexedValues().get(0).getValue(); @@ -478,277 +792,29 @@ class TangemPaymentAccount extends Contract { } public static OwnerSetEventResponse getOwnerSetEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(OWNERSET_EVENT, log); + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(OWNERSET_EVENT, log); OwnerSetEventResponse typedResponse = new OwnerSetEventResponse(); typedResponse.log = log; typedResponse.owner = (String) eventValues.getNonIndexedValues().get(0).getValue(); return typedResponse; } - public static PendingRefundSavedEventResponse getPendingRefundSavedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PENDINGREFUNDSAVED_EVENT, log); - PendingRefundSavedEventResponse typedResponse = new PendingRefundSavedEventResponse(); - typedResponse.log = log; - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; + public Flowable ownerSetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getOwnerSetEventFromLog(log)); } - public static ProcessorSetEventResponse getProcessorSetEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PROCESSORSET_EVENT, log); - ProcessorSetEventResponse typedResponse = new ProcessorSetEventResponse(); - typedResponse.log = log; - typedResponse.processor = (String) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.paymentToken = (String) eventValues.getNonIndexedValues().get(1).getValue(); - return typedResponse; - } - - public static RefundPaidEventResponse getRefundPaidEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(REFUNDPAID_EVENT, log); - RefundPaidEventResponse typedResponse = new RefundPaidEventResponse(); - typedResponse.log = log; - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; - } - - public static TransactionAmountChangedEventResponse getTransactionAmountChangedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSACTIONAMOUNTCHANGED_EVENT, log); - TransactionAmountChangedEventResponse typedResponse = new TransactionAmountChangedEventResponse(); - typedResponse.log = log; - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.newAmount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; - } - - public static TransactionAuthorizedEventResponse getTransactionAuthorizedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSACTIONAUTHORIZED_EVENT, log); - TransactionAuthorizedEventResponse typedResponse = new TransactionAuthorizedEventResponse(); - typedResponse.log = log; - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.otp = (byte[]) eventValues.getNonIndexedValues().get(1).getValue(); - typedResponse.otpCounter = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); - return typedResponse; - } - - public static List getInitializedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(INITIALIZED_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - InitializedEventResponse typedResponse = new InitializedEventResponse(); - typedResponse.log = eventValues.getLog(); - typedResponse.version = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - responses.add(typedResponse); - } - return responses; - } - - public static TransactionSettledEventResponse getTransactionSettledEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSACTIONSETTLED_EVENT, log); - TransactionSettledEventResponse typedResponse = new TransactionSettledEventResponse(); - typedResponse.log = log; - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.settlementAmount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.paymentAmount = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); - return typedResponse; - } - - public static UnsettledTransactionUnblockedEventResponse getUnsettledTransactionUnblockedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(UNSETTLEDTRANSACTIONUNBLOCKED_EVENT, log); - UnsettledTransactionUnblockedEventResponse typedResponse = new UnsettledTransactionUnblockedEventResponse(); - typedResponse.log = log; - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; - } - - public static UpgradedEventResponse getUpgradedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(UPGRADED_EVENT, log); - UpgradedEventResponse typedResponse = new UpgradedEventResponse(); - typedResponse.log = log; - typedResponse.implementation = (String) eventValues.getIndexedValues().get(0).getValue(); - return typedResponse; - } - - public static List getInsufficientFundsOnForcedAuthEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(INSUFFICIENTFUNDSONFORCEDAUTH_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - InsufficientFundsOnForcedAuthEventResponse typedResponse = new InsufficientFundsOnForcedAuthEventResponse(); - typedResponse.log = eventValues.getLog(); - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.insufficientAmount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - responses.add(typedResponse); - } - return responses; - } - - public static VerifiedBalanceIncreasedEventResponse getVerifiedBalanceIncreasedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(VERIFIEDBALANCEINCREASED_EVENT, log); - VerifiedBalanceIncreasedEventResponse typedResponse = new VerifiedBalanceIncreasedEventResponse(); - typedResponse.log = log; - typedResponse.increase = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.verifiedBalance = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); - return typedResponse; - } - - public static VerifiedBalanceSetEventResponse getVerifiedBalanceSetEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(VERIFIEDBALANCESET_EVENT, log); - VerifiedBalanceSetEventResponse typedResponse = new VerifiedBalanceSetEventResponse(); - typedResponse.log = log; - typedResponse.verifiedBalance = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; - } - - public static WithdrawalCanceledEventResponse getWithdrawalCanceledEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(WITHDRAWALCANCELED_EVENT, log); - WithdrawalCanceledEventResponse typedResponse = new WithdrawalCanceledEventResponse(); - typedResponse.log = log; - return typedResponse; - } - - public static List getLimitsSetEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(LIMITSSET_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - LimitsSetEventResponse typedResponse = new LimitsSetEventResponse(); - typedResponse.log = eventValues.getLog(); - typedResponse.singleTransactionLimit = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.spendLimit = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); - typedResponse.noOtpSpendLimit = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); - typedResponse.spendLimitsPeriod = (BigInteger) eventValues.getNonIndexedValues().get(3).getValue(); - responses.add(typedResponse); - } - return responses; - } - - public static WithdrawalCompleteEventResponse getWithdrawalCompleteEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(WITHDRAWALCOMPLETE_EVENT, log); - WithdrawalCompleteEventResponse typedResponse = new WithdrawalCompleteEventResponse(); - typedResponse.log = log; - typedResponse.to = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; - } - - @Deprecated - public static TangemPaymentAccount load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { - return new TangemPaymentAccount(contractAddress, web3j, credentials, gasPrice, gasLimit); - } - - @Deprecated - public static TangemPaymentAccount load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { - return new TangemPaymentAccount(contractAddress, web3j, transactionManager, gasPrice, gasLimit); - } - - public static List getNoOtpTransactionAuthorizedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(NOOTPTRANSACTIONAUTHORIZED_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - NoOtpTransactionAuthorizedEventResponse typedResponse = new NoOtpTransactionAuthorizedEventResponse(); - typedResponse.log = eventValues.getLog(); - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - responses.add(typedResponse); - } - return responses; - } - - public static TangemPaymentAccount load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { - return new TangemPaymentAccount(contractAddress, web3j, credentials, contractGasProvider); - } - - public static TangemPaymentAccount load(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { - return new TangemPaymentAccount(contractAddress, web3j, transactionManager, contractGasProvider); - } - - public static RemoteCall deploy(Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider, String trustedForwarder) { - String encodedConstructor = FunctionEncoder.encodeConstructor(List.of(new Address(160, trustedForwarder))); - return deployRemoteCall(TangemPaymentAccount.class, web3j, credentials, contractGasProvider, BINARY, encodedConstructor); - } - - public static List getOtpStateSetEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(OTPSTATESET_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - OtpStateSetEventResponse typedResponse = new OtpStateSetEventResponse(); - typedResponse.log = eventValues.getLog(); - typedResponse.otpRoot = (byte[]) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.otpCounter = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); - responses.add(typedResponse); - } - return responses; - } - - public static RemoteCall deploy(Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider, String trustedForwarder) { - String encodedConstructor = FunctionEncoder.encodeConstructor(List.of(new Address(160, trustedForwarder))); - return deployRemoteCall(TangemPaymentAccount.class, web3j, transactionManager, contractGasProvider, BINARY, encodedConstructor); - } - - @Deprecated - public static RemoteCall deploy(Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit, String trustedForwarder) { - String encodedConstructor = FunctionEncoder.encodeConstructor(List.of(new Address(160, trustedForwarder))); - return deployRemoteCall(TangemPaymentAccount.class, web3j, credentials, gasPrice, gasLimit, BINARY, encodedConstructor); - } - - @Deprecated - public static RemoteCall deploy(Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit, String trustedForwarder) { - String encodedConstructor = FunctionEncoder.encodeConstructor(List.of(new Address(160, trustedForwarder))); - return deployRemoteCall(TangemPaymentAccount.class, web3j, transactionManager, gasPrice, gasLimit, BINARY, encodedConstructor); - } - - public Flowable accountStateAfterSettlementEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getAccountStateAfterSettlementEventFromLog(log)); - } - - public Flowable accountStateAfterSettlementEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public Flowable ownerSetEventFlowable(DefaultBlockParameter startBlock, + DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(ACCOUNTSTATEAFTERSETTLEMENT_EVENT)); - return accountStateAfterSettlementEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(OWNERSET_EVENT)); + return ownerSetEventFlowable(filter); } - public Flowable cardSetEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getCardSetEventFromLog(log)); - } - - public Flowable cardSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(CARDSET_EVENT)); - return cardSetEventFlowable(filter); - } - - public static List getPendingRefundSavedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(PENDINGREFUNDSAVED_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - PendingRefundSavedEventResponse typedResponse = new PendingRefundSavedEventResponse(); - typedResponse.log = eventValues.getLog(); - typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - responses.add(typedResponse); - } - return responses; - } - - public Flowable debtIncreasedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getDebtIncreasedEventFromLog(log)); - } - - public Flowable debtIncreasedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(DEBTINCREASED_EVENT)); - return debtIncreasedEventFlowable(filter); - } - - public Flowable debtPaidEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getDebtPaidEventFromLog(log)); - } - - public static List getProcessorSetEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(PROCESSORSET_EVENT, transactionReceipt); + public static List getProcessorSetEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(PROCESSORSET_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { ProcessorSetEventResponse typedResponse = new ProcessorSetEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.processor = (String) eventValues.getNonIndexedValues().get(0).getValue(); @@ -758,85 +824,147 @@ class TangemPaymentAccount extends Contract { return responses; } - public Flowable debtPaidEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public static ProcessorSetEventResponse getProcessorSetEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PROCESSORSET_EVENT, log); + ProcessorSetEventResponse typedResponse = new ProcessorSetEventResponse(); + typedResponse.log = log; + typedResponse.processor = (String) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.paymentToken = (String) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; + } + + public Flowable processorSetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getProcessorSetEventFromLog(log)); + } + + public Flowable processorSetEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(DEBTPAID_EVENT)); - return debtPaidEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(PROCESSORSET_EVENT)); + return processorSetEventFlowable(filter); } - public Flowable debtWrittenOffEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getDebtWrittenOffEventFromLog(log)); - } - - public Flowable debtWrittenOffEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(DEBTWRITTENOFF_EVENT)); - return debtWrittenOffEventFlowable(filter); - } - - public static List getRefundPaidEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(REFUNDPAID_EVENT, transactionReceipt); + public static List getRefundPaidEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(REFUNDPAID_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { RefundPaidEventResponse typedResponse = new RefundPaidEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); responses.add(typedResponse); } return responses; } - public Flowable eIP712DomainChangedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getEIP712DomainChangedEventFromLog(log)); + public static RefundPaidEventResponse getRefundPaidEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(REFUNDPAID_EVENT, log); + RefundPaidEventResponse typedResponse = new RefundPaidEventResponse(); + typedResponse.log = log; + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; } - public Flowable eIP712DomainChangedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public Flowable refundPaidEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getRefundPaidEventFromLog(log)); + } + + public Flowable refundPaidEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(EIP712DOMAINCHANGED_EVENT)); - return eIP712DomainChangedEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(REFUNDPAID_EVENT)); + return refundPaidEventFlowable(filter); } - public Flowable initializedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getInitializedEventFromLog(log)); - } - - public static List getTransactionAmountChangedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(TRANSACTIONAMOUNTCHANGED_EVENT, transactionReceipt); + public static List getTransactionAmountChangedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(TRANSACTIONAMOUNTCHANGED_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { TransactionAmountChangedEventResponse typedResponse = new TransactionAmountChangedEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); typedResponse.newAmount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); responses.add(typedResponse); } return responses; } - public Flowable initializedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public static TransactionAmountChangedEventResponse getTransactionAmountChangedEventFromLog( + Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSACTIONAMOUNTCHANGED_EVENT, log); + TransactionAmountChangedEventResponse typedResponse = new TransactionAmountChangedEventResponse(); + typedResponse.log = log; + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.newAmount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable transactionAmountChangedEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getTransactionAmountChangedEventFromLog(log)); + } + + public Flowable transactionAmountChangedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(INITIALIZED_EVENT)); - return initializedEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(TRANSACTIONAMOUNTCHANGED_EVENT)); + return transactionAmountChangedEventFlowable(filter); } - public Flowable insufficientFundsOnForcedAuthEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getInsufficientFundsOnForcedAuthEventFromLog(log)); - } - - public Flowable insufficientFundsOnForcedAuthEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(INSUFFICIENTFUNDSONFORCEDAUTH_EVENT)); - return insufficientFundsOnForcedAuthEventFlowable(filter); - } - - public static List getTransactionAuthorizedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(TRANSACTIONAUTHORIZED_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - TransactionAuthorizedEventResponse typedResponse = new TransactionAuthorizedEventResponse(); + public static List getTransactionAuthorizedNoConfirmationEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(TRANSACTIONAUTHORIZEDNOCONFIRMATION_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + TransactionAuthorizedNoConfirmationEventResponse typedResponse = new TransactionAuthorizedNoConfirmationEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static TransactionAuthorizedNoConfirmationEventResponse getTransactionAuthorizedNoConfirmationEventFromLog( + Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSACTIONAUTHORIZEDNOCONFIRMATION_EVENT, log); + TransactionAuthorizedNoConfirmationEventResponse typedResponse = new TransactionAuthorizedNoConfirmationEventResponse(); + typedResponse.log = log; + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable transactionAuthorizedNoConfirmationEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getTransactionAuthorizedNoConfirmationEventFromLog(log)); + } + + public Flowable transactionAuthorizedNoConfirmationEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(TRANSACTIONAUTHORIZEDNOCONFIRMATION_EVENT)); + return transactionAuthorizedNoConfirmationEventFlowable(filter); + } + + public static List getTransactionAuthorizedWithOtpEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(TRANSACTIONAUTHORIZEDWITHOTP_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + TransactionAuthorizedWithOtpEventResponse typedResponse = new TransactionAuthorizedWithOtpEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); typedResponse.otp = (byte[]) eventValues.getNonIndexedValues().get(1).getValue(); typedResponse.otpCounter = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); @@ -845,27 +973,80 @@ class TangemPaymentAccount extends Contract { return responses; } - public Flowable limitsSetEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getLimitsSetEventFromLog(log)); + public static TransactionAuthorizedWithOtpEventResponse getTransactionAuthorizedWithOtpEventFromLog( + Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSACTIONAUTHORIZEDWITHOTP_EVENT, log); + TransactionAuthorizedWithOtpEventResponse typedResponse = new TransactionAuthorizedWithOtpEventResponse(); + typedResponse.log = log; + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.otp = (byte[]) eventValues.getNonIndexedValues().get(1).getValue(); + typedResponse.otpCounter = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); + return typedResponse; } - public Flowable limitsSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public Flowable transactionAuthorizedWithOtpEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getTransactionAuthorizedWithOtpEventFromLog(log)); + } + + public Flowable transactionAuthorizedWithOtpEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(LIMITSSET_EVENT)); - return limitsSetEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(TRANSACTIONAUTHORIZEDWITHOTP_EVENT)); + return transactionAuthorizedWithOtpEventFlowable(filter); } - public Flowable noOtpTransactionAuthorizedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getNoOtpTransactionAuthorizedEventFromLog(log)); + public static List getTransactionAuthorizedWithSignatureEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(TRANSACTIONAUTHORIZEDWITHSIGNATURE_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + TransactionAuthorizedWithSignatureEventResponse typedResponse = new TransactionAuthorizedWithSignatureEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.signature = (byte[]) eventValues.getNonIndexedValues().get(1).getValue(); + responses.add(typedResponse); + } + return responses; } - public static List getTransactionSettledEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(TRANSACTIONSETTLED_EVENT, transactionReceipt); + public static TransactionAuthorizedWithSignatureEventResponse getTransactionAuthorizedWithSignatureEventFromLog( + Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSACTIONAUTHORIZEDWITHSIGNATURE_EVENT, log); + TransactionAuthorizedWithSignatureEventResponse typedResponse = new TransactionAuthorizedWithSignatureEventResponse(); + typedResponse.log = log; + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.signature = (byte[]) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; + } + + public Flowable transactionAuthorizedWithSignatureEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getTransactionAuthorizedWithSignatureEventFromLog(log)); + } + + public Flowable transactionAuthorizedWithSignatureEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(TRANSACTIONAUTHORIZEDWITHSIGNATURE_EVENT)); + return transactionAuthorizedWithSignatureEventFlowable(filter); + } + + public static List getTransactionSettledEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(TRANSACTIONSETTLED_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { TransactionSettledEventResponse typedResponse = new TransactionSettledEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); typedResponse.settlementAmount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); typedResponse.paymentAmount = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); responses.add(typedResponse); @@ -873,53 +1054,72 @@ class TangemPaymentAccount extends Contract { return responses; } - public Flowable noOtpTransactionAuthorizedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public static TransactionSettledEventResponse getTransactionSettledEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSACTIONSETTLED_EVENT, log); + TransactionSettledEventResponse typedResponse = new TransactionSettledEventResponse(); + typedResponse.log = log; + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.settlementAmount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.paymentAmount = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; + } + + public Flowable transactionSettledEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getTransactionSettledEventFromLog(log)); + } + + public Flowable transactionSettledEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(NOOTPTRANSACTIONAUTHORIZED_EVENT)); - return noOtpTransactionAuthorizedEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(TRANSACTIONSETTLED_EVENT)); + return transactionSettledEventFlowable(filter); } - public Flowable otpStateSetEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getOtpStateSetEventFromLog(log)); - } - - public Flowable otpStateSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(OTPSTATESET_EVENT)); - return otpStateSetEventFlowable(filter); - } - - public static List getUnsettledTransactionUnblockedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(UNSETTLEDTRANSACTIONUNBLOCKED_EVENT, transactionReceipt); + public static List getUnsettledTransactionUnblockedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(UNSETTLEDTRANSACTIONUNBLOCKED_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { UnsettledTransactionUnblockedEventResponse typedResponse = new UnsettledTransactionUnblockedEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); responses.add(typedResponse); } return responses; } - public Flowable ownerSetEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getOwnerSetEventFromLog(log)); + public static UnsettledTransactionUnblockedEventResponse getUnsettledTransactionUnblockedEventFromLog( + Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(UNSETTLEDTRANSACTIONUNBLOCKED_EVENT, log); + UnsettledTransactionUnblockedEventResponse typedResponse = new UnsettledTransactionUnblockedEventResponse(); + typedResponse.log = log; + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; } - public Flowable ownerSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public Flowable unsettledTransactionUnblockedEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getUnsettledTransactionUnblockedEventFromLog(log)); + } + + public Flowable unsettledTransactionUnblockedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(OWNERSET_EVENT)); - return ownerSetEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(UNSETTLEDTRANSACTIONUNBLOCKED_EVENT)); + return unsettledTransactionUnblockedEventFlowable(filter); } - public Flowable pendingRefundSavedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getPendingRefundSavedEventFromLog(log)); - } - - public static List getUpgradedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(UPGRADED_EVENT, transactionReceipt); + public static List getUpgradedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(UPGRADED_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { UpgradedEventResponse typedResponse = new UpgradedEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.implementation = (String) eventValues.getIndexedValues().get(0).getValue(); @@ -928,26 +1128,30 @@ class TangemPaymentAccount extends Contract { return responses; } - public Flowable pendingRefundSavedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public static UpgradedEventResponse getUpgradedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(UPGRADED_EVENT, log); + UpgradedEventResponse typedResponse = new UpgradedEventResponse(); + typedResponse.log = log; + typedResponse.implementation = (String) eventValues.getIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable upgradedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getUpgradedEventFromLog(log)); + } + + public Flowable upgradedEventFlowable(DefaultBlockParameter startBlock, + DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(PENDINGREFUNDSAVED_EVENT)); - return pendingRefundSavedEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(UPGRADED_EVENT)); + return upgradedEventFlowable(filter); } - public Flowable processorSetEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getProcessorSetEventFromLog(log)); - } - - public Flowable processorSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(PROCESSORSET_EVENT)); - return processorSetEventFlowable(filter); - } - - public static List getVerifiedBalanceIncreasedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(VERIFIEDBALANCEINCREASED_EVENT, transactionReceipt); + public static List getVerifiedBalanceIncreasedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(VERIFIEDBALANCEINCREASED_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { VerifiedBalanceIncreasedEventResponse typedResponse = new VerifiedBalanceIncreasedEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.increase = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); @@ -957,24 +1161,33 @@ class TangemPaymentAccount extends Contract { return responses; } - public Flowable refundPaidEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getRefundPaidEventFromLog(log)); + public static VerifiedBalanceIncreasedEventResponse getVerifiedBalanceIncreasedEventFromLog( + Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(VERIFIEDBALANCEINCREASED_EVENT, log); + VerifiedBalanceIncreasedEventResponse typedResponse = new VerifiedBalanceIncreasedEventResponse(); + typedResponse.log = log; + typedResponse.increase = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.verifiedBalance = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; } - public Flowable refundPaidEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public Flowable verifiedBalanceIncreasedEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getVerifiedBalanceIncreasedEventFromLog(log)); + } + + public Flowable verifiedBalanceIncreasedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(REFUNDPAID_EVENT)); - return refundPaidEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(VERIFIEDBALANCEINCREASED_EVENT)); + return verifiedBalanceIncreasedEventFlowable(filter); } - public Flowable transactionAmountChangedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getTransactionAmountChangedEventFromLog(log)); - } - - public static List getVerifiedBalanceSetEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(VERIFIEDBALANCESET_EVENT, transactionReceipt); + public static List getVerifiedBalanceSetEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(VERIFIEDBALANCESET_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { VerifiedBalanceSetEventResponse typedResponse = new VerifiedBalanceSetEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.verifiedBalance = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); @@ -983,51 +1196,66 @@ class TangemPaymentAccount extends Contract { return responses; } - public Flowable transactionAmountChangedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public static VerifiedBalanceSetEventResponse getVerifiedBalanceSetEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(VERIFIEDBALANCESET_EVENT, log); + VerifiedBalanceSetEventResponse typedResponse = new VerifiedBalanceSetEventResponse(); + typedResponse.log = log; + typedResponse.verifiedBalance = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable verifiedBalanceSetEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getVerifiedBalanceSetEventFromLog(log)); + } + + public Flowable verifiedBalanceSetEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(TRANSACTIONAMOUNTCHANGED_EVENT)); - return transactionAmountChangedEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(VERIFIEDBALANCESET_EVENT)); + return verifiedBalanceSetEventFlowable(filter); } - public Flowable transactionAuthorizedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getTransactionAuthorizedEventFromLog(log)); - } - - public Flowable transactionAuthorizedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(TRANSACTIONAUTHORIZED_EVENT)); - return transactionAuthorizedEventFlowable(filter); - } - - public static List getWithdrawalCanceledEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(WITHDRAWALCANCELED_EVENT, transactionReceipt); + public static List getWithdrawalCanceledEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(WITHDRAWALCANCELED_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { WithdrawalCanceledEventResponse typedResponse = new WithdrawalCanceledEventResponse(); typedResponse.log = eventValues.getLog(); + typedResponse.to = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); responses.add(typedResponse); } return responses; } - public Flowable transactionSettledEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getTransactionSettledEventFromLog(log)); + public static WithdrawalCanceledEventResponse getWithdrawalCanceledEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(WITHDRAWALCANCELED_EVENT, log); + WithdrawalCanceledEventResponse typedResponse = new WithdrawalCanceledEventResponse(); + typedResponse.log = log; + typedResponse.to = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; } - public Flowable transactionSettledEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public Flowable withdrawalCanceledEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getWithdrawalCanceledEventFromLog(log)); + } + + public Flowable withdrawalCanceledEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(TRANSACTIONSETTLED_EVENT)); - return transactionSettledEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(WITHDRAWALCANCELED_EVENT)); + return withdrawalCanceledEventFlowable(filter); } - public Flowable unsettledTransactionUnblockedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getUnsettledTransactionUnblockedEventFromLog(log)); - } - - public static List getWithdrawalCompleteEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(WITHDRAWALCOMPLETE_EVENT, transactionReceipt); + public static List getWithdrawalCompleteEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(WITHDRAWALCOMPLETE_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { WithdrawalCompleteEventResponse typedResponse = new WithdrawalCompleteEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.to = (String) eventValues.getIndexedValues().get(0).getValue(); @@ -1037,28 +1265,35 @@ class TangemPaymentAccount extends Contract { return responses; } - public Flowable unsettledTransactionUnblockedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public static WithdrawalCompleteEventResponse getWithdrawalCompleteEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(WITHDRAWALCOMPLETE_EVENT, log); + WithdrawalCompleteEventResponse typedResponse = new WithdrawalCompleteEventResponse(); + typedResponse.log = log; + typedResponse.to = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable withdrawalCompleteEventFlowable( + EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getWithdrawalCompleteEventFromLog(log)); + } + + public Flowable withdrawalCompleteEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(UNSETTLEDTRANSACTIONUNBLOCKED_EVENT)); - return unsettledTransactionUnblockedEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(WITHDRAWALCOMPLETE_EVENT)); + return withdrawalCompleteEventFlowable(filter); } - public Flowable upgradedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getUpgradedEventFromLog(log)); - } - - public Flowable upgradedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(UPGRADED_EVENT)); - return upgradedEventFlowable(filter); - } - - public static List getWithdrawalInitiatedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(WITHDRAWALINITIATED_EVENT, transactionReceipt); + public static List getWithdrawalInitiatedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(WITHDRAWALINITIATED_EVENT, transactionReceipt); ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { + for (Contract.EventValuesWithLog eventValues : valueList) { WithdrawalInitiatedEventResponse typedResponse = new WithdrawalInitiatedEventResponse(); typedResponse.log = eventValues.getLog(); + typedResponse.to = (String) eventValues.getIndexedValues().get(0).getValue(); typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); typedResponse.readyToWithdrawTimestamp = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); responses.add(typedResponse); @@ -1067,175 +1302,176 @@ class TangemPaymentAccount extends Contract { } public static WithdrawalInitiatedEventResponse getWithdrawalInitiatedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(WITHDRAWALINITIATED_EVENT, log); + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(WITHDRAWALINITIATED_EVENT, log); WithdrawalInitiatedEventResponse typedResponse = new WithdrawalInitiatedEventResponse(); typedResponse.log = log; + typedResponse.to = (String) eventValues.getIndexedValues().get(0).getValue(); typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); typedResponse.readyToWithdrawTimestamp = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); return typedResponse; } - public Flowable withdrawalInitiatedEventFlowable(EthFilter filter) { + public Flowable withdrawalInitiatedEventFlowable( + EthFilter filter) { return web3j.ethLogFlowable(filter).map(log -> getWithdrawalInitiatedEventFromLog(log)); } - public Flowable withdrawalInitiatedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public Flowable withdrawalInitiatedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); filter.addSingleTopic(EventEncoder.encode(WITHDRAWALINITIATED_EVENT)); return withdrawalInitiatedEventFlowable(filter); } - public Flowable verifiedBalanceIncreasedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getVerifiedBalanceIncreasedEventFromLog(log)); - } - - public Flowable verifiedBalanceIncreasedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(VERIFIEDBALANCEINCREASED_EVENT)); - return verifiedBalanceIncreasedEventFlowable(filter); - } - - public Flowable verifiedBalanceSetEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getVerifiedBalanceSetEventFromLog(log)); - } - - public Flowable verifiedBalanceSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(VERIFIEDBALANCESET_EVENT)); - return verifiedBalanceSetEventFlowable(filter); - } - - public Flowable withdrawalCanceledEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getWithdrawalCanceledEventFromLog(log)); - } - - public Flowable withdrawalCanceledEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(WITHDRAWALCANCELED_EVENT)); - return withdrawalCanceledEventFlowable(filter); - } - - public Flowable withdrawalCompleteEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getWithdrawalCompleteEventFromLog(log)); - } - - public Flowable withdrawalCompleteEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(WITHDRAWALCOMPLETE_EVENT)); - return withdrawalCompleteEventFlowable(filter); - } - public RemoteFunctionCall UPGRADE_INTERFACE_VERSION() { final Function function = new Function(FUNC_UPGRADE_INTERFACE_VERSION, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, String.class); } - public RemoteFunctionCall> authorizedTransactions(BigInteger param0) { - final Function function = new Function(FUNC_AUTHORIZEDTRANSACTIONS, - List.of(new Uint256(param0)), - Arrays.asList(new TypeReference() { - }, new TypeReference() { - }, new TypeReference() { - })); - return new RemoteFunctionCall>(function, - new Callable>() { + public RemoteFunctionCall activeCardAddresses() { + final Function function = new Function(FUNC_ACTIVECARDADDRESSES, + Arrays.asList(), + Arrays.>asList(new TypeReference>() {})); + return new RemoteFunctionCall(function, + new Callable() { @Override - public Tuple3 call() throws Exception { + @SuppressWarnings("unchecked") + public List call() throws Exception { + List result = (List) executeCallSingleValueReturn(function, List.class); + return convertToNative(result); + } + }); + } + + public RemoteFunctionCall addCard(String cardAddress, CardParams cardParams, + byte[] ownershipAcceptanceSignature, byte[] cardConfirmationSignature) { + final Function function = new Function( + FUNC_ADDCARD, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, cardAddress), + cardParams, + new org.web3j.abi.datatypes.DynamicBytes(ownershipAcceptanceSignature), + new org.web3j.abi.datatypes.DynamicBytes(cardConfirmationSignature)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall authLimitMargin() { + final Function function = new Function(FUNC_AUTHLIMITMARGIN, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall> authorizedTransactions( + BigInteger param0) { + final Function function = new Function(FUNC_AUTHORIZEDTRANSACTIONS, + Arrays.asList(new org.web3j.abi.datatypes.generated.Uint256(param0)), + Arrays.>asList(new TypeReference() {}, new TypeReference
() {}, new TypeReference() {}, new TypeReference() {})); + return new RemoteFunctionCall>(function, + new Callable>() { + @Override + public Tuple4 call() throws Exception { List results = executeCallMultipleValueReturn(function); - return new Tuple3( + return new Tuple4( (BigInteger) results.get(0).getValue(), - (BigInteger) results.get(1).getValue(), - (Boolean) results.get(2).getValue()); + (String) results.get(1).getValue(), + (BigInteger) results.get(2).getValue(), + (Boolean) results.get(3).getValue()); } }); } public RemoteFunctionCall availableForDebtPayment() { final Function function = new Function(FUNC_AVAILABLEFORDEBTPAYMENT, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } public RemoteFunctionCall availableForPayment() { final Function function = new Function(FUNC_AVAILABLEFORPAYMENT, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } public RemoteFunctionCall availableForWithdrawal() { final Function function = new Function(FUNC_AVAILABLEFORWITHDRAWAL, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } public RemoteFunctionCall blockedAmount() { final Function function = new Function(FUNC_BLOCKEDAMOUNT, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } public RemoteFunctionCall cancelWithdrawal() { final Function function = new Function( FUNC_CANCELWITHDRAWAL, - List.of(), - Collections.emptyList()); + Arrays.asList(), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall> cardWithOtp() { - final Function function = new Function(FUNC_CARDWITHOTP, - List.of(), - Arrays.asList(new TypeReference() { - }, new TypeReference() { - }, new TypeReference() { - })); - return new RemoteFunctionCall>(function, - new Callable>() { + public RemoteFunctionCall cardConfirmationNonce() { + final Function function = new Function(FUNC_CARDCONFIRMATIONNONCE, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall> cards( + String param0) { + final Function function = new Function(FUNC_CARDS, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, param0)), + Arrays.>asList(new TypeReference() {}, new TypeReference() {}, new TypeReference() {}, new TypeReference() {}, new TypeReference() {})); + return new RemoteFunctionCall>(function, + new Callable>() { @Override - public Tuple3 call() throws Exception { + public Tuple5 call( + ) throws Exception { List results = executeCallMultipleValueReturn(function); - return new Tuple3( - (CardWithOtp) results.get(0), - (CardWithOtp) results.get(1), - (BigInteger) results.get(2).getValue()); + return new Tuple5( + (Boolean) results.get(0).getValue(), + (Boolean) results.get(1).getValue(), + (BigInteger) results.get(2).getValue(), + (OtpStateSetting) results.get(3), + (LimitsSetting) results.get(4)); } }); } public RemoteFunctionCall debtAmount() { final Function function = new Function(FUNC_DEBTAMOUNT, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } - public RemoteFunctionCall>> eip712Domain() { + public RemoteFunctionCall disableCard(String card) { + final Function function = new Function( + FUNC_DISABLECARD, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, card)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall>> eip712Domain( + ) { final Function function = new Function(FUNC_EIP712DOMAIN, - List.of(), - Arrays.asList(new TypeReference() { - }, new TypeReference() { - }, new TypeReference() { - }, new TypeReference() { - }, new TypeReference
() { - }, new TypeReference() { - }, new TypeReference>() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {}, new TypeReference() {}, new TypeReference() {}, new TypeReference() {}, new TypeReference
() {}, new TypeReference() {}, new TypeReference>() {})); return new RemoteFunctionCall>>(function, new Callable>>() { @Override - public Tuple7> call() throws Exception { + public Tuple7> call( + ) throws Exception { List results = executeCallMultipleValueReturn(function); return new Tuple7>( (byte[]) results.get(0).getValue(), @@ -1249,369 +1485,427 @@ class TangemPaymentAccount extends Contract { }); } + public RemoteFunctionCall enableCard(String card, Boolean isOwner, + byte[] ownershipAcceptanceSignature, byte[] cardConfirmationSignature) { + final Function function = new Function( + FUNC_ENABLECARD, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.Bool(isOwner), + new org.web3j.abi.datatypes.DynamicBytes(ownershipAcceptanceSignature), + new org.web3j.abi.datatypes.DynamicBytes(cardConfirmationSignature)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + public RemoteFunctionCall factory() { final Function function = new Function(FUNC_FACTORY, - List.of(), - List.of(new TypeReference
() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference
() {})); return executeRemoteCallSingleValueReturn(function, String.class); } public RemoteFunctionCall increaseVerifiedBalance(BigInteger increase) { final Function function = new Function( FUNC_INCREASEVERIFIEDBALANCE, - List.of(new Uint256(increase)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.generated.Uint256(increase)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall initWithdrawal(BigInteger amount) { + public RemoteFunctionCall initWithdrawal(String to, BigInteger amount) { final Function function = new Function( FUNC_INITWITHDRAWAL, - List.of(new Uint256(amount)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, to), + new org.web3j.abi.datatypes.generated.Uint256(amount)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall initialize(String owner_, String registry_, BigInteger singleTransactionLimit, BigInteger spendLimit, BigInteger noOtpSpendLimit, BigInteger spendLimitsPeriod) { + public RemoteFunctionCall initialize(String owner_, String processor_, + String registry_, String cardAddress, CardParams cardParams, + BigInteger authLimitMargin_) { final Function function = new Function( FUNC_INITIALIZE, - Arrays.asList(new Address(160, owner_), - new Address(160, registry_), - new Uint256(singleTransactionLimit), - new Uint256(spendLimit), - new Uint256(noOtpSpendLimit), - new Uint256(spendLimitsPeriod)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, owner_), + new org.web3j.abi.datatypes.Address(160, processor_), + new org.web3j.abi.datatypes.Address(160, registry_), + new org.web3j.abi.datatypes.Address(160, cardAddress), + cardParams, + new org.web3j.abi.datatypes.generated.Uint256(authLimitMargin_)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } public RemoteFunctionCall isTrustedForwarder(String forwarder) { final Function function = new Function(FUNC_ISTRUSTEDFORWARDER, - List.of(new Address(160, forwarder)), - List.of(new TypeReference() { - })); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, forwarder)), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, Boolean.class); } public RemoteFunctionCall isWithdrawalInProgress() { final Function function = new Function(FUNC_ISWITHDRAWALINPROGRESS, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, Boolean.class); } public RemoteFunctionCall isWithdrawalReady() { final Function function = new Function(FUNC_ISWITHDRAWALREADY, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, Boolean.class); } - public RemoteFunctionCall> limits() { - final Function function = new Function(FUNC_LIMITS, - List.of(), - Arrays.asList(new TypeReference() { - }, new TypeReference() { - }, new TypeReference() { - })); - return new RemoteFunctionCall>(function, - new Callable>() { - @Override - public Tuple3 call() throws Exception { - List results = executeCallMultipleValueReturn(function); - return new Tuple3( - (Limits) results.get(0), - (Limits) results.get(1), - (BigInteger) results.get(2).getValue()); - } - }); - } - public RemoteFunctionCall owner() { final Function function = new Function(FUNC_OWNER, - List.of(), - List.of(new TypeReference
() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference
() {})); return executeRemoteCallSingleValueReturn(function, String.class); } public RemoteFunctionCall ownershipAcceptanceNonce() { final Function function = new Function(FUNC_OWNERSHIPACCEPTANCENONCE, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } + public RemoteFunctionCall payDebt(BigInteger amount) { + final Function function = new Function( + FUNC_PAYDEBT, + Arrays.asList(new org.web3j.abi.datatypes.generated.Uint256(amount)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + public RemoteFunctionCall paymentToken() { final Function function = new Function(FUNC_PAYMENTTOKEN, - List.of(), - List.of(new TypeReference
() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference
() {})); return executeRemoteCallSingleValueReturn(function, String.class); } - public RemoteFunctionCall pendingRefundAmount(BigInteger transactionId) { - final Function function = new Function(FUNC_PENDINGREFUNDAMOUNT, - List.of(new Uint256(transactionId)), - List.of(new TypeReference() { - })); - return executeRemoteCallSingleValueReturn(function, BigInteger.class); - } - - public RemoteFunctionCall pendingRefundTotal() { - final Function function = new Function(FUNC_PENDINGREFUNDTOTAL, - List.of(), - List.of(new TypeReference() { - })); - return executeRemoteCallSingleValueReturn(function, BigInteger.class); - } - - public RemoteFunctionCall processAuthorization(BigInteger transactionId, BigInteger amount, byte[] otp, BigInteger otpCounter, Boolean forced) { - final Function function = new Function( - FUNC_PROCESSAUTHORIZATION, - Arrays.asList(new Uint256(transactionId), - new Uint256(amount), - new Bytes16(otp), - new Uint16(otpCounter), - new Bool(forced)), - Collections.emptyList()); - return executeRemoteCallTransaction(function); - } - - public RemoteFunctionCall processAuthorizationChange(BigInteger transactionId, BigInteger newAmount) { + public RemoteFunctionCall processAuthorizationChange(String card, + BigInteger transactionId, BigInteger newAmount) { final Function function = new Function( FUNC_PROCESSAUTHORIZATIONCHANGE, - Arrays.asList(new Uint256(transactionId), - new Uint256(newAmount)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(transactionId), + new org.web3j.abi.datatypes.generated.Uint256(newAmount)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall processAuthorizationNoOtp(BigInteger transactionId, BigInteger amount, Boolean forced) { - final Function function = new Function( - FUNC_PROCESSAUTHORIZATIONNOOTP, - Arrays.asList(new Uint256(transactionId), - new Uint256(amount), - new Bool(forced)), - Collections.emptyList()); - return executeRemoteCallTransaction(function); - } - - public RemoteFunctionCall processDebt(BigInteger amount) { + public RemoteFunctionCall processDebt(String card, BigInteger amount) { final Function function = new Function( FUNC_PROCESSDEBT, - List.of(new Uint256(amount)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(amount)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall processPendingRefundPayment(BigInteger transactionId) { + public RemoteFunctionCall processNoConfirmationAuthorization(String card, + BigInteger transactionId, BigInteger amount, Boolean forced) { final Function function = new Function( - FUNC_PROCESSPENDINGREFUNDPAYMENT, - List.of(new Uint256(transactionId)), - Collections.emptyList()); + FUNC_PROCESSNOCONFIRMATIONAUTHORIZATION, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(transactionId), + new org.web3j.abi.datatypes.generated.Uint256(amount), + new org.web3j.abi.datatypes.Bool(forced)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall processRefundPayment(BigInteger transactionId) { + public RemoteFunctionCall processOtpAuthorization(String card, + BigInteger transactionId, BigInteger amount, byte[] otp, BigInteger otpCounter, + Boolean forced) { + final Function function = new Function( + FUNC_PROCESSOTPAUTHORIZATION, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(transactionId), + new org.web3j.abi.datatypes.generated.Uint256(amount), + new org.web3j.abi.datatypes.generated.Bytes16(otp), + new org.web3j.abi.datatypes.generated.Uint16(otpCounter), + new org.web3j.abi.datatypes.Bool(forced)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processRefundPayment(String card, + BigInteger transactionId) { final Function function = new Function( FUNC_PROCESSREFUNDPAYMENT, - List.of(new Uint256(transactionId)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(transactionId)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall processSettlement(BigInteger transactionId, BigInteger amount) { + public RemoteFunctionCall processSettlement(String card, + BigInteger transactionId, BigInteger amount) { final Function function = new Function( FUNC_PROCESSSETTLEMENT, - Arrays.asList(new Uint256(transactionId), - new Uint256(amount)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(transactionId), + new org.web3j.abi.datatypes.generated.Uint256(amount)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall processWithdrawal(String to) { + public RemoteFunctionCall processSignatureAuthorization(String card, + BigInteger transactionId, BigInteger amount, byte[] transactionConfirmationSignature, + Boolean forced) { + final Function function = new Function( + FUNC_PROCESSSIGNATUREAUTHORIZATION, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(transactionId), + new org.web3j.abi.datatypes.generated.Uint256(amount), + new org.web3j.abi.datatypes.DynamicBytes(transactionConfirmationSignature), + new org.web3j.abi.datatypes.Bool(forced)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processUnsettledTransaction(String card, + BigInteger transactionId) { + final Function function = new Function( + FUNC_PROCESSUNSETTLEDTRANSACTION, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.generated.Uint256(transactionId)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processWithdrawal() { final Function function = new Function( FUNC_PROCESSWITHDRAWAL, - List.of(new Address(160, to)), - Collections.emptyList()); + Arrays.asList(), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } public RemoteFunctionCall processor() { final Function function = new Function(FUNC_PROCESSOR, - List.of(), - List.of(new TypeReference
() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference
() {})); return executeRemoteCallSingleValueReturn(function, String.class); } public RemoteFunctionCall proxiableUUID() { final Function function = new Function(FUNC_PROXIABLEUUID, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, byte[].class); } public RemoteFunctionCall registry() { final Function function = new Function(FUNC_REGISTRY, - List.of(), - List.of(new TypeReference
() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference
() {})); return executeRemoteCallSingleValueReturn(function, String.class); } - public RemoteFunctionCall savePendingRefund(BigInteger transactionId, BigInteger amount) { + public RemoteFunctionCall removeOwner() { final Function function = new Function( - FUNC_SAVEPENDINGREFUND, - Arrays.asList(new Uint256(transactionId), - new Uint256(amount)), - Collections.emptyList()); + FUNC_REMOVEOWNER, + Arrays.asList(), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } public RemoteFunctionCall securityDelay() { final Function function = new Function(FUNC_SECURITYDELAY, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } - public RemoteFunctionCall setCard(String card, byte[] otpRoot, BigInteger otpCounter, byte[] ownershipAcceptanceSignature) { + public RemoteFunctionCall setAuthLimitMargin(BigInteger authLimitMargin_) { final Function function = new Function( - FUNC_SETCARD, - Arrays.asList(new Address(160, card), - new Bytes16(otpRoot), - new Uint16(otpCounter), - new org.web3j.abi.datatypes.DynamicBytes(ownershipAcceptanceSignature)), - Collections.emptyList()); + FUNC_SETAUTHLIMITMARGIN, + Arrays.asList(new org.web3j.abi.datatypes.generated.Uint256(authLimitMargin_)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall setLimits(BigInteger singleTransactionLimit, BigInteger spendLimit, BigInteger noOtpSpendLimit, BigInteger spendLimitsPeriod) { + public RemoteFunctionCall setCardIsOwner(String card, Boolean isOwner) { + final Function function = new Function( + FUNC_SETCARDISOWNER, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.Bool(isOwner)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall setLimits(String card, + LimitsParams limitsParams) { final Function function = new Function( FUNC_SETLIMITS, - Arrays.asList(new Uint256(singleTransactionLimit), - new Uint256(spendLimit), - new Uint256(noOtpSpendLimit), - new Uint256(spendLimitsPeriod)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, card), + limitsParams), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall setOtpState(byte[] otp, BigInteger counter) { + public RemoteFunctionCall setOtpState(String card, OtpState otpState) { final Function function = new Function( FUNC_SETOTPSTATE, - Arrays.asList(new Bytes16(otp), - new Uint16(counter)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, card), + otpState), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall setOwner(String owner_, byte[] ownershipAcceptanceSignature) { + public RemoteFunctionCall setOwner(String owner_, + byte[] ownershipAcceptanceSignature) { final Function function = new Function( FUNC_SETOWNER, - Arrays.asList(new Address(160, owner_), + Arrays.asList(new org.web3j.abi.datatypes.Address(160, owner_), new org.web3j.abi.datatypes.DynamicBytes(ownershipAcceptanceSignature)), - Collections.emptyList()); - return executeRemoteCallTransaction(function); - } - - public RemoteFunctionCall setProcessor(String processor_) { - final Function function = new Function( - FUNC_SETPROCESSOR, - List.of(new Address(160, processor_)), - Collections.emptyList()); + Collections.>emptyList()); return executeRemoteCallTransaction(function); } public RemoteFunctionCall setVerifiedBalance(BigInteger verifiedBalance_) { final Function function = new Function( FUNC_SETVERIFIEDBALANCE, - List.of(new Uint256(verifiedBalance_)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.generated.Uint256(verifiedBalance_)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall settlementPeriod() { - final Function function = new Function(FUNC_SETTLEMENTPERIOD, - List.of(), - List.of(new TypeReference() { - })); + public RemoteFunctionCall transactionConfirmationNonce() { + final Function function = new Function(FUNC_TRANSACTIONCONFIRMATIONNONCE, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } public RemoteFunctionCall trustedForwarder() { final Function function = new Function(FUNC_TRUSTEDFORWARDER, - List.of(), - List.of(new TypeReference
() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference
() {})); return executeRemoteCallSingleValueReturn(function, String.class); } - public RemoteFunctionCall unblockUnsettledTransaction(BigInteger transactionId) { - final Function function = new Function( - FUNC_UNBLOCKUNSETTLEDTRANSACTION, - List.of(new Uint256(transactionId)), - Collections.emptyList()); - return executeRemoteCallTransaction(function); - } - - public RemoteFunctionCall upgradeToAndCall(String newImplementation, byte[] data, BigInteger weiValue) { + public RemoteFunctionCall upgradeToAndCall(String newImplementation, + byte[] data, BigInteger weiValue) { final Function function = new Function( FUNC_UPGRADETOANDCALL, - Arrays.asList(new Address(160, newImplementation), + Arrays.asList(new org.web3j.abi.datatypes.Address(160, newImplementation), new org.web3j.abi.datatypes.DynamicBytes(data)), - Collections.emptyList()); + Collections.>emptyList()); return executeRemoteCallTransaction(function, weiValue); } public RemoteFunctionCall verifiedBalance() { final Function function = new Function(FUNC_VERIFIEDBALANCE, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } + public RemoteFunctionCall withdrawalAddress() { + final Function function = new Function(FUNC_WITHDRAWALADDRESS, + Arrays.asList(), + Arrays.>asList(new TypeReference
() {})); + return executeRemoteCallSingleValueReturn(function, String.class); + } + public RemoteFunctionCall withdrawalAmount() { final Function function = new Function(FUNC_WITHDRAWALAMOUNT, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } public RemoteFunctionCall withdrawalReadyTimestamp() { final Function function = new Function(FUNC_WITHDRAWALREADYTIMESTAMP, - List.of(), - List.of(new TypeReference() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); return executeRemoteCallSingleValueReturn(function, BigInteger.class); } public RemoteFunctionCall writeOffDebt(BigInteger amount) { final Function function = new Function( FUNC_WRITEOFFDEBT, - List.of(new Uint256(amount)), - Collections.emptyList()); + Arrays.asList(new org.web3j.abi.datatypes.generated.Uint256(amount)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } + @Deprecated + public static TangemPaymentAccount load(String contractAddress, Web3j web3j, + Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + return new TangemPaymentAccount(contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + @Deprecated + public static TangemPaymentAccount load(String contractAddress, Web3j web3j, + TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + return new TangemPaymentAccount(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + public static TangemPaymentAccount load(String contractAddress, Web3j web3j, + Credentials credentials, ContractGasProvider contractGasProvider) { + return new TangemPaymentAccount(contractAddress, web3j, credentials, contractGasProvider); + } + + public static TangemPaymentAccount load(String contractAddress, Web3j web3j, + TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + return new TangemPaymentAccount(contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static RemoteCall deploy(Web3j web3j, Credentials credentials, + ContractGasProvider contractGasProvider, String trustedForwarder) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Address(160, trustedForwarder))); + return deployRemoteCall(TangemPaymentAccount.class, web3j, credentials, contractGasProvider, getDeploymentBinary(), encodedConstructor); + } + + public static RemoteCall deploy(Web3j web3j, + TransactionManager transactionManager, ContractGasProvider contractGasProvider, + String trustedForwarder) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Address(160, trustedForwarder))); + return deployRemoteCall(TangemPaymentAccount.class, web3j, transactionManager, contractGasProvider, getDeploymentBinary(), encodedConstructor); + } + + @Deprecated + public static RemoteCall deploy(Web3j web3j, Credentials credentials, + BigInteger gasPrice, BigInteger gasLimit, String trustedForwarder) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Address(160, trustedForwarder))); + return deployRemoteCall(TangemPaymentAccount.class, web3j, credentials, gasPrice, gasLimit, getDeploymentBinary(), encodedConstructor); + } + + @Deprecated + public static RemoteCall deploy(Web3j web3j, + TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit, + String trustedForwarder) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Address(160, trustedForwarder))); + return deployRemoteCall(TangemPaymentAccount.class, web3j, transactionManager, gasPrice, gasLimit, getDeploymentBinary(), encodedConstructor); + } + + public static void linkLibraries(List references) { + librariesLinkedBinary = linkBinaryWithReferences(BINARY, references); + } + + private static String getDeploymentBinary() { + if (librariesLinkedBinary != null) { + return librariesLinkedBinary; + } else { + return BINARY; + } + } + public static class OtpState extends StaticStruct { public byte[] otp; public BigInteger counter; public OtpState(byte[] otp, BigInteger counter) { - super(new Bytes16(otp), - new Uint16(counter)); + super(new org.web3j.abi.datatypes.generated.Bytes16(otp), + new org.web3j.abi.datatypes.generated.Uint16(counter)); this.otp = otp; this.counter = counter; } @@ -1623,22 +1917,53 @@ class TangemPaymentAccount extends Contract { } } - public static class Limit extends StaticStruct { - public BigInteger _00_limit; + public static class LimitsParams extends StaticStruct { + public BigInteger singleTransactionLimit; - public BigInteger _01_spent; + public BigInteger spendLimit; + + public BigInteger noConfirmationSpendLimit; + + public BigInteger spendLimitsPeriod; + + public LimitsParams(BigInteger singleTransactionLimit, BigInteger spendLimit, + BigInteger noConfirmationSpendLimit, BigInteger spendLimitsPeriod) { + super(new org.web3j.abi.datatypes.generated.Uint256(singleTransactionLimit), + new org.web3j.abi.datatypes.generated.Uint256(spendLimit), + new org.web3j.abi.datatypes.generated.Uint256(noConfirmationSpendLimit), + new org.web3j.abi.datatypes.generated.Uint256(spendLimitsPeriod)); + this.singleTransactionLimit = singleTransactionLimit; + this.spendLimit = spendLimit; + this.noConfirmationSpendLimit = noConfirmationSpendLimit; + this.spendLimitsPeriod = spendLimitsPeriod; + } + + public LimitsParams(Uint256 singleTransactionLimit, Uint256 spendLimit, + Uint256 noConfirmationSpendLimit, Uint256 spendLimitsPeriod) { + super(singleTransactionLimit, spendLimit, noConfirmationSpendLimit, spendLimitsPeriod); + this.singleTransactionLimit = singleTransactionLimit.getValue(); + this.spendLimit = spendLimit.getValue(); + this.noConfirmationSpendLimit = noConfirmationSpendLimit.getValue(); + this.spendLimitsPeriod = spendLimitsPeriod.getValue(); + } + } + + public static class Limit extends StaticStruct { + public BigInteger limit; + + public BigInteger spent; public Limit(BigInteger limit, BigInteger spent) { - super(new Uint256(limit), - new Uint256(spent)); - this._00_limit = limit; - this._01_spent = spent; + super(new org.web3j.abi.datatypes.generated.Uint256(limit), + new org.web3j.abi.datatypes.generated.Uint256(spent)); + this.limit = limit; + this.spent = spent; } public Limit(Uint256 limit, Uint256 spent) { super(limit, spent); - this._00_limit = limit.getValue(); - this._01_spent = spent.getValue(); + this.limit = limit.getValue(); + this.spent = spent.getValue(); } } @@ -1646,7 +1971,7 @@ class TangemPaymentAccount extends Contract { public BigInteger expireTimestamp; public Timer(BigInteger expireTimestamp) { - super(new Uint256(expireTimestamp)); + super(new org.web3j.abi.datatypes.generated.Uint256(expireTimestamp)); this.expireTimestamp = expireTimestamp; } @@ -1656,56 +1981,112 @@ class TangemPaymentAccount extends Contract { } } - public static class CardWithOtp extends StaticStruct { - public String card; + public static class CardParams extends StaticStruct { + public Boolean isOwner; public OtpState otpState; - public CardWithOtp(String card, OtpState otpState) { - super(new Address(160, card), - otpState); - this.card = card; + public LimitsParams limitsParams; + + public CardParams(Boolean isOwner, OtpState otpState, LimitsParams limitsParams) { + super(new org.web3j.abi.datatypes.Bool(isOwner), + otpState, + limitsParams); + this.isOwner = isOwner; this.otpState = otpState; + this.limitsParams = limitsParams; } - public CardWithOtp(Address card, OtpState otpState) { - super(card, otpState); - this.card = card.getValue(); + public CardParams(Bool isOwner, OtpState otpState, LimitsParams limitsParams) { + super(isOwner, otpState, limitsParams); + this.isOwner = isOwner.getValue(); this.otpState = otpState; + this.limitsParams = limitsParams; + } + } + + public static class OtpStateSetting extends StaticStruct { + public OtpState oldValue; + + public OtpState newValue; + + public BigInteger changeTimestamp; + + public OtpStateSetting(OtpState oldValue, OtpState newValue, BigInteger changeTimestamp) { + super(oldValue, + newValue, + new org.web3j.abi.datatypes.generated.Uint256(changeTimestamp)); + this.oldValue = oldValue; + this.newValue = newValue; + this.changeTimestamp = changeTimestamp; + } + + public OtpStateSetting(OtpState oldValue, OtpState newValue, Uint256 changeTimestamp) { + super(oldValue, newValue, changeTimestamp); + this.oldValue = oldValue; + this.newValue = newValue; + this.changeTimestamp = changeTimestamp.getValue(); } } public static class Limits extends StaticStruct { - public BigInteger _00_singleTransactionLimit; + public BigInteger singleTransactionLimit; - public Limit _01_spendLimit; + public Limit spendLimit; - public Limit _02_noOtpSpendLimit; + public Limit noConfirmationSpendLimit; - public Timer _03_spendLimitsTimer; + public Timer spendLimitsTimer; - public BigInteger _04_spendLimitsPeriod; + public BigInteger spendLimitsPeriod; - public Limits(BigInteger singleTransactionLimit, Limit spendLimit, Limit noOtpSpendLimit, Timer spendLimitsTimer, BigInteger spendLimitsPeriod) { - super(new Uint256(singleTransactionLimit), + public Limits(BigInteger singleTransactionLimit, Limit spendLimit, + Limit noConfirmationSpendLimit, Timer spendLimitsTimer, + BigInteger spendLimitsPeriod) { + super(new org.web3j.abi.datatypes.generated.Uint256(singleTransactionLimit), spendLimit, - noOtpSpendLimit, - spendLimitsTimer, - new Uint256(spendLimitsPeriod)); - this._00_singleTransactionLimit = singleTransactionLimit; - this._01_spendLimit = spendLimit; - this._02_noOtpSpendLimit = noOtpSpendLimit; - this._03_spendLimitsTimer = spendLimitsTimer; - this._04_spendLimitsPeriod = spendLimitsPeriod; + noConfirmationSpendLimit, + spendLimitsTimer, + new org.web3j.abi.datatypes.generated.Uint256(spendLimitsPeriod)); + this.singleTransactionLimit = singleTransactionLimit; + this.spendLimit = spendLimit; + this.noConfirmationSpendLimit = noConfirmationSpendLimit; + this.spendLimitsTimer = spendLimitsTimer; + this.spendLimitsPeriod = spendLimitsPeriod; } - public Limits(Uint256 singleTransactionLimit, Limit spendLimit, Limit noOtpSpendLimit, Timer spendLimitsTimer, Uint256 spendLimitsPeriod) { - super(singleTransactionLimit, spendLimit, noOtpSpendLimit, spendLimitsTimer, spendLimitsPeriod); - this._00_singleTransactionLimit = singleTransactionLimit.getValue(); - this._01_spendLimit = spendLimit; - this._02_noOtpSpendLimit = noOtpSpendLimit; - this._03_spendLimitsTimer = spendLimitsTimer; - this._04_spendLimitsPeriod = spendLimitsPeriod.getValue(); + public Limits(Uint256 singleTransactionLimit, Limit spendLimit, + Limit noConfirmationSpendLimit, Timer spendLimitsTimer, Uint256 spendLimitsPeriod) { + super(singleTransactionLimit, spendLimit, noConfirmationSpendLimit, spendLimitsTimer, spendLimitsPeriod); + this.singleTransactionLimit = singleTransactionLimit.getValue(); + this.spendLimit = spendLimit; + this.noConfirmationSpendLimit = noConfirmationSpendLimit; + this.spendLimitsTimer = spendLimitsTimer; + this.spendLimitsPeriod = spendLimitsPeriod.getValue(); + } + } + + public static class LimitsSetting extends StaticStruct { + public Limits oldValue; + + public Limits newValue; + + public BigInteger changeTimestamp; + + public LimitsSetting(Limits oldValue, Limits newValue, BigInteger changeTimestamp) { + super(oldValue, + newValue, + new org.web3j.abi.datatypes.generated.Uint256(changeTimestamp)); + this.oldValue = oldValue; + this.newValue = newValue; + this.changeTimestamp = changeTimestamp; + } + + public LimitsSetting(Limits oldValue, Limits newValue, Uint256 changeTimestamp) { + super(oldValue, newValue, changeTimestamp); + this.oldValue = oldValue; + this.newValue = newValue; + this.changeTimestamp = changeTimestamp.getValue(); } } @@ -1715,16 +2096,26 @@ class TangemPaymentAccount extends Contract { public BigInteger blockedAmount; public BigInteger debtTotal; - - public BigInteger pendingRefundTotal; } - public static class CardSetEventResponse extends BaseEventResponse { + public static class AuthLimitMarginSetEventResponse extends BaseEventResponse { + public BigInteger authLimitMargin; + } + + public static class CardAddedEventResponse extends BaseEventResponse { + public String cardAddress; + + public CardParams cardData; + } + + public static class CardDisabledEventResponse extends BaseEventResponse { + public String card; + } + + public static class CardEnabledEventResponse extends BaseEventResponse { public String card; - public byte[] otpRoot; - - public BigInteger otpCounter; + public Boolean isOwner; } public static class DebtIncreasedEventResponse extends BaseEventResponse { @@ -1755,41 +2146,31 @@ class TangemPaymentAccount extends Contract { public static class InsufficientFundsOnForcedAuthEventResponse extends BaseEventResponse { public BigInteger transactionId; + public String card; + public BigInteger insufficientAmount; } public static class LimitsSetEventResponse extends BaseEventResponse { - public BigInteger singleTransactionLimit; + public String card; - public BigInteger spendLimit; - - public BigInteger noOtpSpendLimit; - - public BigInteger spendLimitsPeriod; - } - - public static class NoOtpTransactionAuthorizedEventResponse extends BaseEventResponse { - public BigInteger transactionId; - - public BigInteger amount; + public LimitsParams limitsParams; } public static class OtpStateSetEventResponse extends BaseEventResponse { - public byte[] otpRoot; + public String card; - public BigInteger otpCounter; + public OtpState otpState; + } + + public static class OwnerRemovedEventResponse extends BaseEventResponse { + public String previousOwner; } public static class OwnerSetEventResponse extends BaseEventResponse { public String owner; } - public static class PendingRefundSavedEventResponse extends BaseEventResponse { - public BigInteger transactionId; - - public BigInteger amount; - } - public static class ProcessorSetEventResponse extends BaseEventResponse { public String processor; @@ -1799,18 +2180,32 @@ class TangemPaymentAccount extends Contract { public static class RefundPaidEventResponse extends BaseEventResponse { public BigInteger transactionId; + public String card; + public BigInteger amount; } public static class TransactionAmountChangedEventResponse extends BaseEventResponse { public BigInteger transactionId; + public String card; + public BigInteger newAmount; } - public static class TransactionAuthorizedEventResponse extends BaseEventResponse { + public static class TransactionAuthorizedNoConfirmationEventResponse extends BaseEventResponse { public BigInteger transactionId; + public String card; + + public BigInteger amount; + } + + public static class TransactionAuthorizedWithOtpEventResponse extends BaseEventResponse { + public BigInteger transactionId; + + public String card; + public BigInteger amount; public byte[] otp; @@ -1818,9 +2213,21 @@ class TangemPaymentAccount extends Contract { public BigInteger otpCounter; } + public static class TransactionAuthorizedWithSignatureEventResponse extends BaseEventResponse { + public BigInteger transactionId; + + public String card; + + public BigInteger amount; + + public byte[] signature; + } + public static class TransactionSettledEventResponse extends BaseEventResponse { public BigInteger transactionId; + public String card; + public BigInteger settlementAmount; public BigInteger paymentAmount; @@ -1829,6 +2236,8 @@ class TangemPaymentAccount extends Contract { public static class UnsettledTransactionUnblockedEventResponse extends BaseEventResponse { public BigInteger transactionId; + public String card; + public BigInteger amount; } @@ -1847,6 +2256,9 @@ class TangemPaymentAccount extends Contract { } public static class WithdrawalCanceledEventResponse extends BaseEventResponse { + public String to; + + public BigInteger amount; } public static class WithdrawalCompleteEventResponse extends BaseEventResponse { @@ -1856,8 +2268,10 @@ class TangemPaymentAccount extends Contract { } public static class WithdrawalInitiatedEventResponse extends BaseEventResponse { + public String to; + public BigInteger amount; public BigInteger readyToWithdrawTimestamp; } -} +} \ No newline at end of file diff --git a/libs/visa/src/main/java/com/tangem/lib/visa/TangemPaymentAccountRegistry.java b/libs/visa/src/main/java/com/tangem/lib/visa/TangemPaymentAccountRegistry.java index bcf93417f6..a5107bee89 100644 --- a/libs/visa/src/main/java/com/tangem/lib/visa/TangemPaymentAccountRegistry.java +++ b/libs/visa/src/main/java/com/tangem/lib/visa/TangemPaymentAccountRegistry.java @@ -1,9 +1,20 @@ package com.tangem.lib.visa; +import io.reactivex.Flowable; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; import org.web3j.abi.EventEncoder; import org.web3j.abi.FunctionEncoder; import org.web3j.abi.TypeReference; -import org.web3j.abi.datatypes.*; +import org.web3j.abi.datatypes.Address; +import org.web3j.abi.datatypes.DynamicArray; +import org.web3j.abi.datatypes.Event; +import org.web3j.abi.datatypes.Function; +import org.web3j.abi.datatypes.Type; import org.web3j.crypto.Credentials; import org.web3j.protocol.Web3j; import org.web3j.protocol.core.DefaultBlockParameter; @@ -17,111 +28,89 @@ import org.web3j.tx.Contract; import org.web3j.tx.TransactionManager; import org.web3j.tx.gas.ContractGasProvider; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.Callable; - -import io.reactivex.Flowable; - /** *

Auto generated code. *

Do not modify! *

Please use the web3j command line tools, * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the - * codegen module to update. + * codegen module to update. * - *

Generated with web3j version 1.5.2. + *

Generated with web3j version 1.6.1. */ @SuppressWarnings("rawtypes") -class TangemPaymentAccountRegistry extends Contract { - public static final String BINARY = "608060405234801561001057600080fd5b50604051610c47380380610c4783398101604081905261002f91610054565b600080546001600160a01b0319166001600160a01b0392909216919091179055610084565b60006020828403121561006657600080fd5b81516001600160a01b038116811461007d57600080fd5b9392505050565b610bb4806100936000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c806368d7111a1161005b57806368d7111a146100f0578063747fc3701461010357806379da564f14610116578063c45a01551461013657600080fd5b80630cef7172146100825780633509c7c5146100c85780635cd26737146100dd575b600080fd5b6100ab6100903660046108fd565b6002602052600090815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100db6100d6366004610918565b610149565b005b6100db6100eb366004610918565b610201565b6100db6100fe3660046108fd565b6103b0565b6100db610111366004610918565b610506565b6101296101243660046108fd565b6106db565b6040516100bf919061094b565b6000546100ab906001600160a01b031681565b600054604080516080810190915260428082526001600160a01b03909216331491610a576020830139906101995760405162461bcd60e51b81526004016101909190610998565b60405180910390fd5b506001600160a01b03811660009081526001602052604090206101bc9083610705565b50806001600160a01b0316826001600160a01b03167f65b303fd420aefd0541d9300bb10190bb8e3320a0e7c2ce4dec6b6bfb953027f60405160405180910390a35050565b6000546040516385bb392360e01b81523360048201526001600160a01b03909116906385bb392390602401602060405180830381865afa158015610249573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061026d91906109e7565b6040518060600160405280603a8152602001610b45603a9139906102a45760405162461bcd60e51b81526004016101909190610998565b506001600160a01b038216600090815260016020526040902033906102c99082610721565b6040518060600160405280603b8152602001610ad0603b9139906103005760405162461bcd60e51b81526004016101909190610998565b506001600160a01b03821660009081526001602052604090206103239082610705565b6040518060600160405280603b8152602001610ad0603b91399061035a5760405162461bcd60e51b81526004016101909190610998565b50604080516001600160a01b03808416825280861660208301528416918101919091527ffba993cae385d1da628b11b1b150a27262bca00bb2674980516b45ad7d331eaa906060015b60405180910390a1505050565b6000546040516385bb392360e01b81523360048201526001600160a01b03909116906385bb392390602401602060405180830381865afa1580156103f8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061041c91906109e7565b6040518060600160405280603a8152602001610b45603a9139906104535760405162461bcd60e51b81526004016101909190610998565b506001600160a01b0381811660009081526002602090815260409182902054825160608101909352603a8084529316159290610b0b90830139906104aa5760405162461bcd60e51b81526004016101909190610998565b506001600160a01b03811660008181526002602052604080822080546001600160a01b03191633908117909155905190929183917f02e4debb2ab36299bfcc998345f66f73428adbbda9ef5efef399d75a67a4b78a9190a35050565b6000546040516385bb392360e01b81523360048201526001600160a01b03909116906385bb392390602401602060405180830381865afa15801561054e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061057291906109e7565b6040518060600160405280603a8152602001610b45603a9139906105a95760405162461bcd60e51b81526004016101909190610998565b506001600160a01b0382811660009081526002602090815260409182902054825160608101909352603780845233949190911684149291610a9990830139906106055760405162461bcd60e51b81526004016101909190610998565b506001600160a01b0382811660009081526002602090815260409182902054825160608101909352603a8084529316159290610b0b908301399061065c5760405162461bcd60e51b81526004016101909190610998565b506001600160a01b03838116600081815260026020908152604080832080546001600160a01b03199081169091558786168085529382902080549091169587169586179055805194855290840192909252908201527f362e9cf018894fbf5dfe9c5cbfb25c3e3ac8c4d5beed564352f1af2e2712dfc8906060016103a3565b6001600160a01b03811660009081526001602052604090206060906106ff90610736565b92915050565b600061071a836001600160a01b038416610743565b9392505050565b600061071a836001600160a01b038416610792565b6060600061071a83610885565b600081815260018301602052604081205461078a575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556106ff565b5060006106ff565b6000818152600183016020526040812054801561087b5760006107b6600183610a09565b85549091506000906107ca90600190610a09565b905080821461082f5760008660000182815481106107ea576107ea610a2a565b906000526020600020015490508087600001848154811061080d5761080d610a2a565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061084057610840610a40565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506106ff565b60009150506106ff565b6060816000018054806020026020016040519081016040528092919081815260200182805480156108d557602002820191906000526020600020905b8154815260200190600101908083116108c1575b50505050509050919050565b80356001600160a01b03811681146108f857600080fd5b919050565b60006020828403121561090f57600080fd5b61071a826108e1565b6000806040838503121561092b57600080fd5b610934836108e1565b9150610942602084016108e1565b90509250929050565b6020808252825182820181905260009190848201906040850190845b8181101561098c5783516001600160a01b031683529284019291840191600101610967565b50909695505050505050565b60006020808352835180602085015260005b818110156109c6578581018301518582016040015282016109aa565b506000604082860101526040601f19601f8301168501019250505092915050565b6000602082840312156109f957600080fd5b8151801515811461071a57600080fd5b818103818111156106ff57634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052603160045260246000fdfe353630307c52656769737472793a206f6e6c79207061796d656e74206163636f756e7420666163746f72792063616e2063616c6c20746869732066756e6374696f6e353630347c52656769737472793a2070726576696f757320636172642773207061796d656e74206163636f756e74206d69736d61746368353630327c52656769737472793a207061796d656e74206163636f756e74206265696e672072656d6f766564206e6f7420696e2074686520736574353630347c52656769737472793a207061796d656e74206163636f756e7420697320616c72656164792073657420666f72207468652063617264353630317c52656769737472793a206f6e6c79207061796d656e74206163636f756e742063616e2063616c6c20746869732066756e6374696f6ea26469706673582212203c97923f6f14a45f904087dcdced3b09f33cd32821438cd9cbf0add5824acaf064736f6c63430008160033"; +public class TangemPaymentAccountRegistry extends Contract { + public static final String BINARY = "60806040523462000030576200001e62000018620000d3565b62000156565b6040516110ab6200016e82396110ab90f35b600080fd5b634e487b7160e01b600052604160045260246000fd5b90601f01601f191681019081106001600160401b038211176200006d57604052565b62000035565b906200008a6200008260405190565b92836200004b565b565b6001600160a01b031690565b90565b6001600160a01b038116036200003057565b905051906200008a826200009b565b9060208282031262000030576200009891620000ad565b620000986200121980380380620000ea8162000073565b928339810190620000bc565b62000098906200008c906001600160a01b031682565b6200009890620000f6565b62000098906200010c565b906200013662000098620001529262000117565b82546001600160a01b0319166001600160a01b03919091161790565b9055565b620001656200008a9162000117565b60006200012256fe6080604052600436101561001257600080fd5b60003560e01c80630cef7172146100b25780630d009297146100ad578063173825d9146100a85780633dc50952146100a35780634149177b1461009e57806379da564f14610099578063a727bc9014610094578063c45a01551461008f578063f00d4b5d1461008a5763f72a1107036100d857610353565b61033a565b610313565b6102cb565b610281565b6101f5565b6101dd565b6101c5565b6101a8565b61016f565b6001600160a01b031690565b90565b6001600160a01b0381165b036100d857565b600080fd5b905035906100ea826100c6565b565b906020828203126100d8576100c3916100dd565b6100c3906100b7906001600160a01b031682565b6100c390610100565b6100c390610114565b906101309061011d565b600052602052604060002090565b6100c3916008021c6100b7565b906100c3915461013e565b60006101666100c3926002610126565b61014b565b9052565b346100d8576101a461018a6101853660046100ec565b610156565b604051918291826001600160a01b03909116815260200190565b0390f35b346100d8576101c06101bb3660046100ec565b6105da565b604051005b346100d8576101c06101d83660046100ec565b610709565b346100d8576101c06101f03660046100ec565b61076f565b346100d8576101c06102083660046100ec565b6108f0565b0190565b9061023161022a610220845190565b8084529260200190565b9260200190565b9060005b8181106102425750505090565b90919261026861026160019286516001600160a01b0316815260200190565b9460200190565b929101610235565b60208082526100c392910190610211565b346100d8576101a461029c6102973660046100ec565b6108f9565b60405191829182610270565b91906040838203126100d8576100c39060206102c482866100dd565b94016100dd565b346100d8576101c06102de3660046102a8565b906109b8565b60009103126100d857565b6100c360008061014b565b61016b9061011d565b6020810192916100ea91906102fa565b346100d8576103233660046102e4565b6101a461032e6102ef565b60405191829182610303565b346100d8576101c061034d3660046102a8565b90610b01565b346100d8576101c06103663660046102a8565b90610b2d565b6100c3906100b7565b6100c3905461036c565b634e487b7160e01b600052604160045260246000fd5b90601f01601f1916810190811067ffffffffffffffff8211176103b757604052565b61037f565b8015156100d1565b905051906100ea826103bc565b906020828203126100d8576100c3916103c4565b6040513d6000823e3d90fd5b906100ea6103fe60405190565b9283610395565b67ffffffffffffffff81116103b757602090601f01601f19160190565b9061043461042f83610405565b6103f1565b918252565b610443603a610422565b7f353630317c52656769737472793a206f6e6c79207061796d656e74206163636f60208201527f756e742063616e2063616c6c20746869732066756e6374696f6e000000000000604082015290565b6100c3610439565b6100c3610492565b60005b8381106104b55750506000910152565b81810151838201526020016104a5565b6104e66104ef60209361020d936104da815190565b80835293849260200190565b958691016104a2565b601f01601f191690565b60208082526100c3929101906104c5565b156105125750565b6105349061051f60405190565b62461bcd60e51b8152918291600483016104f9565b0390fd5b61054a6105456000610375565b61011d565b90602061055660405190565b6385bb392360e01b815233600482015292839060249082905afa9182156105ca576100ea926105969160009161059b575b5061059061049a565b9061050a565b6105cf565b6105bd915060203d6020116105c3575b6105b58183610395565b8101906103d1565b38610587565b503d6105ab565b6103e5565b6100ea903390610b37565b6100ea90610538565b6105f06105456000610375565b9060206105fc60405190565b6385bb392360e01b815233600482015292839060249082905afa9182156105ca576100ea926106359160009161059b575061059061049a565b6106a3565b610644603b610422565b7f353630327c52656769737472793a207061796d656e74206163636f756e74206260208201527f65696e672072656d6f766564206e6f7420696e20746865207365740000000000604082015290565b6100c361063a565b6100c3610693565b6106c66106be6106b76100c3846001610126565b3390610ba3565b61059061069b565b6106d86106d23361011d565b9161011d565b907fe594d081b4382713733fe631966432c9cea5199afb2db5c3c1931f9f9300367961070360405190565b600090a3565b6100ea906105e3565b61071f6105456000610375565b90602061072b60405190565b6385bb392360e01b815233600482015292839060249082905afa9182156105ca576100ea926107649160009161059b575061059061049a565b6100ea903390610c7e565b6100ea90610712565b6107856105456000610375565b90602061079160405190565b6385bb392360e01b815233600482015292839060249082905afa9182156105ca576100ea926107ca9160009161059b575061059061049a565b610873565b6107d96030610422565b7f353630357c52656769737472793a2063617264206265696e672072656d6f766560208201526f19081b9bdd081a5b881d1a19481cd95d60821b604082015290565b6100c36107cf565b6100c361081b565b916001600160a01b0360089290920291821b911b5b9181191691161790565b919061085b6100c36108639361011d565b90835461082b565b9055565b6100ea9160009161084a565b6108a4610889610884836002610126565b610375565b61089b335b916001600160a01b031690565b14610590610823565b6108b960006108b4836002610126565b610867565b6108c56106d23361011d565b907f5f8ec0a3d11bc25dab3cc6b87b3add8f1797a221531def932c4d242f561abf2561070360405190565b6100ea90610778565b6109136100c36100c39261090b606090565b506001610126565b610cef565b6109226042610422565b7f353630307c52656769737472793a206f6e6c79207061796d656e74206163636f60208201527f756e7420666163746f72792063616e2063616c6c20746869732066756e63746960408201526137b760f11b606082015290565b6100c3610918565b6100c361097c565b906100ea916109ae6109a46100b76105456000610375565b3314610590610984565b906100ea91610c7e565b906100ea9161098c565b906109d06105456000610375565b9160206109dc60405190565b6385bb392360e01b815233600482015293849060249082905afa9283156105ca576100ea93610a159160009161059b575061059061049a565b610a83565b610a246040610422565b7f353630337c52656769737472793a207061796d656e74206163636f756e74206260208201527f65696e6720616464656420697320616c726561647920696e2074686520736574604082015290565b6100c3610a1a565b6100c3610a73565b90610a986106be6106b76100c3856001610126565b610abb610ab3610aac6100c3846001610126565b3390610d00565b610590610a7b565b610ad06106d2610aca3361011d565b9361011d565b917f381c0d11398486654573703c51ee8210ce9461764d133f9f0e53b6a539705331610afb60405190565b600090a4565b906100ea916109c2565b906100ea91610b236109a46100b76105456000610375565b906100ea91610b37565b906100ea91610b0b565b906106d2610b5991610545610ab382610b546100c3886001610126565b610d00565b907fa5e1f8b4009110f5525798d04ae2125421a12d0590aa52c13682ff1bd3c492ca61070360405190565b6100c39081906001600160a01b031681565b6100c36100c36100c39290565b90610bd4610bd0610bcb610bc660006100c396610bbe600090565b500194610114565b610b84565b610b96565b9190565b610e66565b6100b76100c36100c39290565b6100c390610bd9565b610bf9603a610422565b7f353630347c52656769737472793a207061796d656e74206163636f756e74206960208201527f7320616c72656164792073657420666f72207468652063617264000000000000604082015290565b6100c3610bef565b6100c3610c48565b906001600160a01b0390610840565b90610c776100c36108639261011d565b8254610c58565b906106d2610cc491610cb0610c97610884866002610126565b610ca761088e6100b76000610be6565b14610590610c50565b61054581610cbf866002610126565b610c67565b907fc063fc300750e8c5649c6b3779f6c4b05e7b38b170ee5c4a4b222ede9a28808361070360405190565b606090610cfb90610fc8565b905090565b90610d1b610bd0610bcb610bc660006100c396610bbe600090565b611005565b90610130565b6100c39081565b6100c39054610d26565b634e487b7160e01b600052601160045260246000fd5b91908203918211610d5a57565b610d37565b634e487b7160e01b600052603260045260246000fd5b8054821015610d9857610d8f600191600052602060002090565b91020190600090565b610d5f565b6100c3916008021c81565b906100c39154610d9d565b9160001960089290920291821b911b610840565b9190610dd66100c36108639390565b908354610db3565b9060001990610840565b90610df86100c361086392610b96565b8254610dde565b634e487b7160e01b600052603160045260246000fd5b6100ea91600091610dc7565b80548015610e44576000190190610e41610e3b8383610d75565b90610e15565b55565b610dff565b9190610dd66100c361086393610b96565b6100ea91600091610e49565b90610e7c610e778260018501610d20565b610d2d565b610e866000610b96565b8114610f3457610eeb6100c392600092610ee095610ee56001978893610eb4610eae86610b96565b82610d4d565b88850191610ed2610ec3845490565b610ecc89610b96565b90610d4d565b808303610ef0575b50505090565b610e21565b01610d20565b610e5a565b610f19610f1f610f2c94610f10610f0a610f279589610d75565b90610da8565b92839188610d75565b90610dc7565b888801610d20565b610de8565b388080610eda565b505050600090565b90610f57610f4b610220845490565b92600052602060002090565b9060005b818110610f685750505090565b909192610f8c610f85600192610f7d87610d2d565b815260200190565b9460010190565b929101610f5b565b906100c391610f3c565b906100ea610fb892610faf60405190565b93848092610f94565b0383610395565b6100c390610f9e565b60006100c391610fd6606090565b5001610fbf565b90815491680100000000000000008310156103b75782610f199160016100ea95018155610d75565b611016611012838361104a565b1590565b156110435761103e91610f27906001611037846110338482610fdd565b5490565b9301610d20565b600190565b5050600090565b611063916001610e779261105c600090565b5001610d20565b611070610bd06000610b96565b14159056fea26469706673582212205e9abf37b9af5365c966f2cb4bdb949d15aac5e8ec856dbc6a6c8fc7e9de783764736f6c63430008160033"; - public static final String FUNC_CHANGEPAYMENTACCOUNTCARD = "changePaymentAccountCard"; + private static String librariesLinkedBinary; - public static final String FUNC_CHANGEPAYMENTACCOUNTOWNER = "changePaymentAccountOwner"; + public static final String FUNC_ADDCARD = "addCard"; + + public static final String FUNC_ADDCARDONDEPLOY = "addCardOnDeploy"; + + public static final String FUNC_CHANGEOWNER = "changeOwner"; public static final String FUNC_FACTORY = "factory"; - public static final String FUNC_INITPAYMENTACCOUNTCARD = "initPaymentAccountCard"; + public static final String FUNC_INITOWNER = "initOwner"; - public static final String FUNC_INITPAYMENTACCOUNTOWNER = "initPaymentAccountOwner"; + public static final String FUNC_INITOWNERONDEPLOY = "initOwnerOnDeploy"; public static final String FUNC_PAYMENTACCOUNTBYCARD = "paymentAccountByCard"; public static final String FUNC_PAYMENTACCOUNTSBYOWNER = "paymentAccountsByOwner"; - public static final Event PAYMENTACCOUNTCARDCHANGED_EVENT = new Event("PaymentAccountCardChanged", - Arrays.asList(new TypeReference

() { - }, new TypeReference
() { - }, new TypeReference
() { - })); + public static final String FUNC_REMOVECARD = "removeCard"; - public static final Event PAYMENTACCOUNTCARDREGISTERED_EVENT = new Event("PaymentAccountCardRegistered", - Arrays.asList(new TypeReference
(true) { - }, new TypeReference
(true) { - })); + public static final String FUNC_REMOVEOWNER = "removeOwner"; - public static final Event PAYMENTACCOUNTOWNERCHANGED_EVENT = new Event("PaymentAccountOwnerChanged", - Arrays.asList(new TypeReference
() { - }, new TypeReference
() { - }, new TypeReference
() { - })); + public static final Event CARDREGISTERED_EVENT = new Event("CardRegistered", + Arrays.>asList(new TypeReference
(true) {}, new TypeReference
(true) {})); + ; - public static final Event PAYMENTACCOUNTOWNERREGISTERED_EVENT = new Event("PaymentAccountOwnerRegistered", - Arrays.asList(new TypeReference
(true) { - }, new TypeReference
(true) { - })); + public static final Event CARDREMOVED_EVENT = new Event("CardRemoved", + Arrays.>asList(new TypeReference
(true) {}, new TypeReference
(true) {})); + ; + + public static final Event OWNERCHANGED_EVENT = new Event("OwnerChanged", + Arrays.>asList(new TypeReference
(true) {}, new TypeReference
(true) {}, new TypeReference
(true) {})); + ; + + public static final Event OWNERREGISTERED_EVENT = new Event("OwnerRegistered", + Arrays.>asList(new TypeReference
(true) {}, new TypeReference
(true) {})); + ; + + public static final Event OWNERREMOVED_EVENT = new Event("OwnerRemoved", + Arrays.>asList(new TypeReference
(true) {}, new TypeReference
(true) {})); + ; @Deprecated - protected TangemPaymentAccountRegistry(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + protected TangemPaymentAccountRegistry(String contractAddress, Web3j web3j, + Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); } - protected TangemPaymentAccountRegistry(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + protected TangemPaymentAccountRegistry(String contractAddress, Web3j web3j, + Credentials credentials, ContractGasProvider contractGasProvider) { super(BINARY, contractAddress, web3j, credentials, contractGasProvider); } @Deprecated - protected TangemPaymentAccountRegistry(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + protected TangemPaymentAccountRegistry(String contractAddress, Web3j web3j, + TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); } - protected TangemPaymentAccountRegistry(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + protected TangemPaymentAccountRegistry(String contractAddress, Web3j web3j, + TransactionManager transactionManager, ContractGasProvider contractGasProvider) { super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); } - public static List getPaymentAccountCardChangedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(PAYMENTACCOUNTCARDCHANGED_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - PaymentAccountCardChangedEventResponse typedResponse = new PaymentAccountCardChangedEventResponse(); - typedResponse.log = eventValues.getLog(); - typedResponse.paymentAccount = (String) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.previousCard = (String) eventValues.getNonIndexedValues().get(1).getValue(); - typedResponse.newCard = (String) eventValues.getNonIndexedValues().get(2).getValue(); - responses.add(typedResponse); - } - return responses; - } - - public static PaymentAccountCardChangedEventResponse getPaymentAccountCardChangedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PAYMENTACCOUNTCARDCHANGED_EVENT, log); - PaymentAccountCardChangedEventResponse typedResponse = new PaymentAccountCardChangedEventResponse(); - typedResponse.log = log; - typedResponse.paymentAccount = (String) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.previousCard = (String) eventValues.getNonIndexedValues().get(1).getValue(); - typedResponse.newCard = (String) eventValues.getNonIndexedValues().get(2).getValue(); - return typedResponse; - } - - public static List getPaymentAccountCardRegisteredEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(PAYMENTACCOUNTCARDREGISTERED_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - PaymentAccountCardRegisteredEventResponse typedResponse = new PaymentAccountCardRegisteredEventResponse(); + public static List getCardRegisteredEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(CARDREGISTERED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + CardRegisteredEventResponse typedResponse = new CardRegisteredEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); @@ -130,44 +119,102 @@ class TangemPaymentAccountRegistry extends Contract { return responses; } - public static PaymentAccountCardRegisteredEventResponse getPaymentAccountCardRegisteredEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PAYMENTACCOUNTCARDREGISTERED_EVENT, log); - PaymentAccountCardRegisteredEventResponse typedResponse = new PaymentAccountCardRegisteredEventResponse(); + public static CardRegisteredEventResponse getCardRegisteredEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(CARDREGISTERED_EVENT, log); + CardRegisteredEventResponse typedResponse = new CardRegisteredEventResponse(); typedResponse.log = log; typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); return typedResponse; } - public static List getPaymentAccountOwnerChangedEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(PAYMENTACCOUNTOWNERCHANGED_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - PaymentAccountOwnerChangedEventResponse typedResponse = new PaymentAccountOwnerChangedEventResponse(); + public Flowable cardRegisteredEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getCardRegisteredEventFromLog(log)); + } + + public Flowable cardRegisteredEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(CARDREGISTERED_EVENT)); + return cardRegisteredEventFlowable(filter); + } + + public static List getCardRemovedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(CARDREMOVED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + CardRemovedEventResponse typedResponse = new CardRemovedEventResponse(); typedResponse.log = eventValues.getLog(); - typedResponse.paymentAccount = (String) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.previousOwner = (String) eventValues.getNonIndexedValues().get(1).getValue(); - typedResponse.newOwner = (String) eventValues.getNonIndexedValues().get(2).getValue(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); responses.add(typedResponse); } return responses; } - public static PaymentAccountOwnerChangedEventResponse getPaymentAccountOwnerChangedEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PAYMENTACCOUNTOWNERCHANGED_EVENT, log); - PaymentAccountOwnerChangedEventResponse typedResponse = new PaymentAccountOwnerChangedEventResponse(); + public static CardRemovedEventResponse getCardRemovedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(CARDREMOVED_EVENT, log); + CardRemovedEventResponse typedResponse = new CardRemovedEventResponse(); typedResponse.log = log; - typedResponse.paymentAccount = (String) eventValues.getNonIndexedValues().get(0).getValue(); - typedResponse.previousOwner = (String) eventValues.getNonIndexedValues().get(1).getValue(); - typedResponse.newOwner = (String) eventValues.getNonIndexedValues().get(2).getValue(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.card = (String) eventValues.getIndexedValues().get(1).getValue(); return typedResponse; } - public static List getPaymentAccountOwnerRegisteredEvents(TransactionReceipt transactionReceipt) { - List valueList = staticExtractEventParametersWithLog(PAYMENTACCOUNTOWNERREGISTERED_EVENT, transactionReceipt); - ArrayList responses = new ArrayList(valueList.size()); - for (EventValuesWithLog eventValues : valueList) { - PaymentAccountOwnerRegisteredEventResponse typedResponse = new PaymentAccountOwnerRegisteredEventResponse(); + public Flowable cardRemovedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getCardRemovedEventFromLog(log)); + } + + public Flowable cardRemovedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(CARDREMOVED_EVENT)); + return cardRemovedEventFlowable(filter); + } + + public static List getOwnerChangedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(OWNERCHANGED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + OwnerChangedEventResponse typedResponse = new OwnerChangedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.previousOwner = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.newOwner = (String) eventValues.getIndexedValues().get(2).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static OwnerChangedEventResponse getOwnerChangedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(OWNERCHANGED_EVENT, log); + OwnerChangedEventResponse typedResponse = new OwnerChangedEventResponse(); + typedResponse.log = log; + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.previousOwner = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.newOwner = (String) eventValues.getIndexedValues().get(2).getValue(); + return typedResponse; + } + + public Flowable ownerChangedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getOwnerChangedEventFromLog(log)); + } + + public Flowable ownerChangedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(OWNERCHANGED_EVENT)); + return ownerChangedEventFlowable(filter); + } + + public static List getOwnerRegisteredEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(OWNERREGISTERED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + OwnerRegisteredEventResponse typedResponse = new OwnerRegisteredEventResponse(); typedResponse.log = eventValues.getLog(); typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); typedResponse.owner = (String) eventValues.getIndexedValues().get(1).getValue(); @@ -176,151 +223,124 @@ class TangemPaymentAccountRegistry extends Contract { return responses; } - public static PaymentAccountOwnerRegisteredEventResponse getPaymentAccountOwnerRegisteredEventFromLog(Log log) { - EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PAYMENTACCOUNTOWNERREGISTERED_EVENT, log); - PaymentAccountOwnerRegisteredEventResponse typedResponse = new PaymentAccountOwnerRegisteredEventResponse(); + public static OwnerRegisteredEventResponse getOwnerRegisteredEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(OWNERREGISTERED_EVENT, log); + OwnerRegisteredEventResponse typedResponse = new OwnerRegisteredEventResponse(); typedResponse.log = log; typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); typedResponse.owner = (String) eventValues.getIndexedValues().get(1).getValue(); return typedResponse; } - @Deprecated - public static TangemPaymentAccountRegistry load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { - return new TangemPaymentAccountRegistry(contractAddress, web3j, credentials, gasPrice, gasLimit); + public Flowable ownerRegisteredEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getOwnerRegisteredEventFromLog(log)); } - @Deprecated - public static TangemPaymentAccountRegistry load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { - return new TangemPaymentAccountRegistry(contractAddress, web3j, transactionManager, gasPrice, gasLimit); - } - - public static TangemPaymentAccountRegistry load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { - return new TangemPaymentAccountRegistry(contractAddress, web3j, credentials, contractGasProvider); - } - - public static TangemPaymentAccountRegistry load(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { - return new TangemPaymentAccountRegistry(contractAddress, web3j, transactionManager, contractGasProvider); - } - - public static RemoteCall deploy(Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider, String factory_) { - String encodedConstructor = FunctionEncoder.encodeConstructor(List.of(new Address(160, factory_))); - return deployRemoteCall(TangemPaymentAccountRegistry.class, web3j, credentials, contractGasProvider, BINARY, encodedConstructor); - } - - public static RemoteCall deploy(Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider, String factory_) { - String encodedConstructor = FunctionEncoder.encodeConstructor(List.of(new Address(160, factory_))); - return deployRemoteCall(TangemPaymentAccountRegistry.class, web3j, transactionManager, contractGasProvider, BINARY, encodedConstructor); - } - - @Deprecated - public static RemoteCall deploy(Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit, String factory_) { - String encodedConstructor = FunctionEncoder.encodeConstructor(List.of(new Address(160, factory_))); - return deployRemoteCall(TangemPaymentAccountRegistry.class, web3j, credentials, gasPrice, gasLimit, BINARY, encodedConstructor); - } - - @Deprecated - public static RemoteCall deploy(Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit, String factory_) { - String encodedConstructor = FunctionEncoder.encodeConstructor(List.of(new Address(160, factory_))); - return deployRemoteCall(TangemPaymentAccountRegistry.class, web3j, transactionManager, gasPrice, gasLimit, BINARY, encodedConstructor); - } - - public Flowable paymentAccountCardChangedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getPaymentAccountCardChangedEventFromLog(log)); - } - - public Flowable paymentAccountCardChangedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public Flowable ownerRegisteredEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(PAYMENTACCOUNTCARDCHANGED_EVENT)); - return paymentAccountCardChangedEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(OWNERREGISTERED_EVENT)); + return ownerRegisteredEventFlowable(filter); } - public Flowable paymentAccountCardRegisteredEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getPaymentAccountCardRegisteredEventFromLog(log)); + public static List getOwnerRemovedEvents( + TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(OWNERREMOVED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + OwnerRemovedEventResponse typedResponse = new OwnerRemovedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.owner = (String) eventValues.getIndexedValues().get(1).getValue(); + responses.add(typedResponse); + } + return responses; } - public Flowable paymentAccountCardRegisteredEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + public static OwnerRemovedEventResponse getOwnerRemovedEventFromLog(Log log) { + Contract.EventValuesWithLog eventValues = staticExtractEventParametersWithLog(OWNERREMOVED_EVENT, log); + OwnerRemovedEventResponse typedResponse = new OwnerRemovedEventResponse(); + typedResponse.log = log; + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.owner = (String) eventValues.getIndexedValues().get(1).getValue(); + return typedResponse; + } + + public Flowable ownerRemovedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getOwnerRemovedEventFromLog(log)); + } + + public Flowable ownerRemovedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(PAYMENTACCOUNTCARDREGISTERED_EVENT)); - return paymentAccountCardRegisteredEventFlowable(filter); + filter.addSingleTopic(EventEncoder.encode(OWNERREMOVED_EVENT)); + return ownerRemovedEventFlowable(filter); } - public Flowable paymentAccountOwnerChangedEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getPaymentAccountOwnerChangedEventFromLog(log)); - } - - public Flowable paymentAccountOwnerChangedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(PAYMENTACCOUNTOWNERCHANGED_EVENT)); - return paymentAccountOwnerChangedEventFlowable(filter); - } - - public Flowable paymentAccountOwnerRegisteredEventFlowable(EthFilter filter) { - return web3j.ethLogFlowable(filter).map(log -> getPaymentAccountOwnerRegisteredEventFromLog(log)); - } - - public Flowable paymentAccountOwnerRegisteredEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); - filter.addSingleTopic(EventEncoder.encode(PAYMENTACCOUNTOWNERREGISTERED_EVENT)); - return paymentAccountOwnerRegisteredEventFlowable(filter); - } - - public RemoteFunctionCall changePaymentAccountCard(String previousCard, String newCard) { + public RemoteFunctionCall addCard(String card) { final Function function = new Function( - FUNC_CHANGEPAYMENTACCOUNTCARD, - Arrays.asList(new Address(160, previousCard), - new Address(160, newCard)), - Collections.emptyList()); + FUNC_ADDCARD, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, card)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall changePaymentAccountOwner(String previousOwner, String newOwner) { + public RemoteFunctionCall addCardOnDeploy(String card, + String paymentAccount) { final Function function = new Function( - FUNC_CHANGEPAYMENTACCOUNTOWNER, - Arrays.asList(new Address(160, previousOwner), - new Address(160, newOwner)), - Collections.emptyList()); + FUNC_ADDCARDONDEPLOY, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, card), + new org.web3j.abi.datatypes.Address(160, paymentAccount)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall changeOwner(String previousOwner, + String newOwner) { + final Function function = new Function( + FUNC_CHANGEOWNER, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, previousOwner), + new org.web3j.abi.datatypes.Address(160, newOwner)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } public RemoteFunctionCall factory() { final Function function = new Function(FUNC_FACTORY, - List.of(), - List.of(new TypeReference
() { - })); + Arrays.asList(), + Arrays.>asList(new TypeReference
() {})); return executeRemoteCallSingleValueReturn(function, String.class); } - public RemoteFunctionCall initPaymentAccountCard(String card) { + public RemoteFunctionCall initOwner(String owner) { final Function function = new Function( - FUNC_INITPAYMENTACCOUNTCARD, - List.of(new Address(160, card)), - Collections.emptyList()); + FUNC_INITOWNER, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, owner)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } - public RemoteFunctionCall initPaymentAccountOwner(String paymentAccount, String owner) { + public RemoteFunctionCall initOwnerOnDeploy(String owner, + String paymentAccount) { final Function function = new Function( - FUNC_INITPAYMENTACCOUNTOWNER, - Arrays.asList(new Address(160, paymentAccount), - new Address(160, owner)), - Collections.emptyList()); + FUNC_INITOWNERONDEPLOY, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, owner), + new org.web3j.abi.datatypes.Address(160, paymentAccount)), + Collections.>emptyList()); return executeRemoteCallTransaction(function); } public RemoteFunctionCall paymentAccountByCard(String param0) { final Function function = new Function(FUNC_PAYMENTACCOUNTBYCARD, - List.of(new Address(160, param0)), - List.of(new TypeReference
() { - })); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, param0)), + Arrays.>asList(new TypeReference
() {})); return executeRemoteCallSingleValueReturn(function, String.class); } public RemoteFunctionCall paymentAccountsByOwner(String owner) { final Function function = new Function(FUNC_PAYMENTACCOUNTSBYOWNER, - List.of(new Address(160, owner)), - List.of(new TypeReference>() { - })); + Arrays.asList(new org.web3j.abi.datatypes.Address(160, owner)), + Arrays.>asList(new TypeReference>() {})); return new RemoteFunctionCall(function, new Callable() { @Override @@ -332,21 +352,97 @@ class TangemPaymentAccountRegistry extends Contract { }); } - public static class PaymentAccountCardChangedEventResponse extends BaseEventResponse { - public String paymentAccount; - - public String previousCard; - - public String newCard; + public RemoteFunctionCall removeCard(String card) { + final Function function = new Function( + FUNC_REMOVECARD, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, card)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); } - public static class PaymentAccountCardRegisteredEventResponse extends BaseEventResponse { + public RemoteFunctionCall removeOwner(String owner) { + final Function function = new Function( + FUNC_REMOVEOWNER, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, owner)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + @Deprecated + public static TangemPaymentAccountRegistry load(String contractAddress, Web3j web3j, + Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + return new TangemPaymentAccountRegistry(contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + @Deprecated + public static TangemPaymentAccountRegistry load(String contractAddress, Web3j web3j, + TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + return new TangemPaymentAccountRegistry(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + public static TangemPaymentAccountRegistry load(String contractAddress, Web3j web3j, + Credentials credentials, ContractGasProvider contractGasProvider) { + return new TangemPaymentAccountRegistry(contractAddress, web3j, credentials, contractGasProvider); + } + + public static TangemPaymentAccountRegistry load(String contractAddress, Web3j web3j, + TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + return new TangemPaymentAccountRegistry(contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static RemoteCall deploy(Web3j web3j, + Credentials credentials, ContractGasProvider contractGasProvider, String factory_) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Address(160, factory_))); + return deployRemoteCall(TangemPaymentAccountRegistry.class, web3j, credentials, contractGasProvider, getDeploymentBinary(), encodedConstructor); + } + + public static RemoteCall deploy(Web3j web3j, + TransactionManager transactionManager, ContractGasProvider contractGasProvider, + String factory_) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Address(160, factory_))); + return deployRemoteCall(TangemPaymentAccountRegistry.class, web3j, transactionManager, contractGasProvider, getDeploymentBinary(), encodedConstructor); + } + + @Deprecated + public static RemoteCall deploy(Web3j web3j, + Credentials credentials, BigInteger gasPrice, BigInteger gasLimit, String factory_) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Address(160, factory_))); + return deployRemoteCall(TangemPaymentAccountRegistry.class, web3j, credentials, gasPrice, gasLimit, getDeploymentBinary(), encodedConstructor); + } + + @Deprecated + public static RemoteCall deploy(Web3j web3j, + TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit, + String factory_) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Address(160, factory_))); + return deployRemoteCall(TangemPaymentAccountRegistry.class, web3j, transactionManager, gasPrice, gasLimit, getDeploymentBinary(), encodedConstructor); + } + + public static void linkLibraries(List references) { + librariesLinkedBinary = linkBinaryWithReferences(BINARY, references); + } + + private static String getDeploymentBinary() { + if (librariesLinkedBinary != null) { + return librariesLinkedBinary; + } else { + return BINARY; + } + } + + public static class CardRegisteredEventResponse extends BaseEventResponse { public String paymentAccount; public String card; } - public static class PaymentAccountOwnerChangedEventResponse extends BaseEventResponse { + public static class CardRemovedEventResponse extends BaseEventResponse { + public String paymentAccount; + + public String card; + } + + public static class OwnerChangedEventResponse extends BaseEventResponse { public String paymentAccount; public String previousOwner; @@ -354,9 +450,15 @@ class TangemPaymentAccountRegistry extends Contract { public String newOwner; } - public static class PaymentAccountOwnerRegisteredEventResponse extends BaseEventResponse { + public static class OwnerRegisteredEventResponse extends BaseEventResponse { public String paymentAccount; public String owner; } -} + + public static class OwnerRemovedEventResponse extends BaseEventResponse { + public String paymentAccount; + + public String owner; + } +} \ No newline at end of file diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/DefaultVisaContractInfoProvider.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/DefaultVisaContractInfoProvider.kt index 312f708435..395904554e 100644 --- a/libs/visa/src/main/kotlin/com/tangem/lib/visa/DefaultVisaContractInfoProvider.kt +++ b/libs/visa/src/main/kotlin/com/tangem/lib/visa/DefaultVisaContractInfoProvider.kt @@ -20,27 +20,38 @@ internal class DefaultVisaContractInfoProvider( private val dispatchers: CoroutineDispatcherProvider, ) : VisaContractInfoProvider { - override suspend fun getContractInfo(walletAddress: String): VisaContractInfo { + override suspend fun getContractInfo(walletAddress: String, paymentAccountAddress: String?): VisaContractInfo { return parZip( dispatchers.io, - { loadPaymentAccount(walletAddress) }, + { loadPaymentAccount(walletAddress = walletAddress, paymentAccountAddress = paymentAccountAddress) }, { loadPaymentTokenInfo() }, { paymentAccount, paymentToken -> - fetchBalancesAndLimits(paymentAccount, paymentToken) + fetchBalancesAndLimits( + paymentAccount = paymentAccount, + paymentToken = paymentToken, + walletAddress = walletAddress, + ) }, ) } - private fun loadPaymentAccount(walletAddress: String): TangemPaymentAccount { + private fun loadPaymentAccount(walletAddress: String, paymentAccountAddress: String?): TangemPaymentAccount { + return TangemPaymentAccount.load( + /* contractAddress = */ paymentAccountAddress ?: getPaymentAccountAddressFromRegistry(walletAddress), + /* web3j = */ web3j, + /* transactionManager = */ transactionManager, + /* contractGasProvider = */ gasProvider, + ) + } + + private fun getPaymentAccountAddressFromRegistry(walletAddress: String): String { val paymentAccountRegistry = TangemPaymentAccountRegistry.load( /* contractAddress = */ paymentAccountRegistryAddress, /* web3j = */ web3j, /* transactionManager = */ transactionManager, /* contractGasProvider = */ gasProvider, ) - val paymentAccountAddress = paymentAccountRegistry.paymentAccountByCard(walletAddress).send() - - return TangemPaymentAccount.load(paymentAccountAddress, web3j, transactionManager, gasProvider) + return paymentAccountRegistry.paymentAccountByCard(walletAddress).send() } private fun loadPaymentTokenInfo(): PaymentTokenInfo { @@ -63,11 +74,12 @@ internal class DefaultVisaContractInfoProvider( private suspend fun fetchBalancesAndLimits( paymentAccount: TangemPaymentAccount, paymentToken: PaymentTokenInfo, + walletAddress: String, ): VisaContractInfo = parZip( dispatchers.io, { fetchToken(paymentAccount) }, { fetchBalances(paymentAccount, paymentToken) }, - { fetchLimits(paymentAccount, paymentToken) }, + { fetchLimits(paymentAccount, paymentToken, walletAddress) }, { token, balances, (oldLimit, newLimit, changeDate) -> VisaContractInfo(token, balances, oldLimit, newLimit, changeDate) }, @@ -102,8 +114,7 @@ internal class DefaultVisaContractInfoProvider( { paymentAccount.availableForDebtPayment().send() }, { paymentAccount.blockedAmount().send() }, { paymentAccount.debtAmount().send() }, - { paymentAccount.pendingRefundTotal().send() }, - ) { total, verified, payment, withdrawal, debtPayment, blocked, debt, refund -> + ) { total, verified, payment, withdrawal, debtPayment, blocked, debt -> val decimals = paymentToken.decimals Balances( @@ -116,40 +127,38 @@ internal class DefaultVisaContractInfoProvider( ), blocked = blocked.toBigDecimal(decimals), debt = debt.toBigDecimal(decimals), - pendingRefund = refund.toBigDecimal(decimals), ) } } + @Suppress("UnusedPrivateMember") private fun fetchLimits( paymentAccount: TangemPaymentAccount, paymentToken: PaymentTokenInfo, + walletAddress: String, ): Triple { - val ( - oldLimit, - newLimit, - changeDateSeconds, - ) = paymentAccount.limits().send() + val limits = paymentAccount.cards(walletAddress).send().component5() return Triple( - first = getLimits(oldLimit, paymentToken), - second = getLimits(newLimit, paymentToken), - third = changeDateSeconds.toInstant(), + first = getLimits(limits.oldValue, paymentToken), + second = getLimits(limits.newValue, paymentToken), + third = limits.changeTimestamp.toInstant(), ) } + @Suppress("UnusedPrivateMember") private fun getLimits(limit: TangemPaymentAccount.Limits, paymentToken: PaymentTokenInfo): Limits = Limits( - spendLimit = limit._01_spendLimit.toLimit(paymentToken.decimals), - noOtpLimit = limit._02_noOtpSpendLimit.toLimit(paymentToken.decimals), - singleTransactionLimit = limit._00_singleTransactionLimit.toBigDecimal(paymentToken.decimals), - expirationDate = limit._03_spendLimitsTimer.expireTimestamp.toInstant(), - spendPeriodSeconds = limit._04_spendLimitsPeriod, + spendLimit = limit.spendLimit.toLimit(paymentToken.decimals), + noOtpLimit = limit.noConfirmationSpendLimit.toLimit(paymentToken.decimals), + singleTransactionLimit = limit.singleTransactionLimit.toBigDecimal(paymentToken.decimals), + expirationDate = limit.spendLimitsTimer.expireTimestamp.toInstant(), + spendPeriodSeconds = limit.spendLimitsPeriod, ) private fun TangemPaymentAccount.Limit.toLimit(decimals: Int): Limits.Limit { return Limits.Limit( - limit = _00_limit.toBigDecimal(decimals), - spent = _01_spent.toBigDecimal(decimals), + limit = limit.toBigDecimal(decimals), + spent = spent.toBigDecimal(decimals), ) } diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/VisaContractInfoProvider.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/VisaContractInfoProvider.kt index 4696b698fb..4587bfabf1 100644 --- a/libs/visa/src/main/kotlin/com/tangem/lib/visa/VisaContractInfoProvider.kt +++ b/libs/visa/src/main/kotlin/com/tangem/lib/visa/VisaContractInfoProvider.kt @@ -21,7 +21,14 @@ import java.util.concurrent.TimeUnit interface VisaContractInfoProvider { - suspend fun getContractInfo(walletAddress: String): VisaContractInfo + /** + * Fetches Visa contract info for the given wallet address. + * + * @param walletAddress Wallet address to fetch contract info for. + * @param paymentAccountAddress Payment account address to fetch data from. If null, + * it will be fetched from the registry. + */ + suspend fun getContractInfo(walletAddress: String, paymentAccountAddress: String?): VisaContractInfo class Builder( private val useTestnetRpc: Boolean, diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApi.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApi.kt deleted file mode 100644 index 106ced7a60..0000000000 --- a/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApi.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.lib.visa.api - -import com.tangem.datasource.api.common.response.ApiResponse -import com.tangem.lib.visa.model.VisaTxHistoryResponse -import retrofit2.http.GET -import retrofit2.http.Header -import retrofit2.http.Query - -interface VisaApi { - - @GET("transaction") - suspend fun getTxHistory( - @Header("Authorization") authorizationHeader: String, - @Query("card_public_key") cardPublicKey: String, - @Query("limit") limit: Int, - @Query("offset") offset: Int, - ): ApiResponse -} \ No newline at end of file diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApiBuilder.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApiBuilder.kt deleted file mode 100644 index ef1a99dd5f..0000000000 --- a/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApiBuilder.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.tangem.lib.visa.api - -import android.util.Log -import com.ihsanbal.logging.Level -import com.ihsanbal.logging.LoggingInterceptor -import com.squareup.moshi.Moshi -import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory -import com.tangem.lib.visa.utils.Constants -import com.tangem.lib.visa.utils.Constants.NETWORK_LOGS_TAG -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import retrofit2.Retrofit -import retrofit2.converter.moshi.MoshiConverterFactory -import java.util.concurrent.TimeUnit - -class VisaApiBuilder( - private val useDevApi: Boolean, - private val isNetworkLoggingEnabled: Boolean, - private val moshi: Moshi, - private val headers: Map, - private val networkTimeoutSeconds: Long = Constants.NETWORK_TIMEOUT_SECONDS, -) { - - fun build(): VisaApi { - val okHttpClient = createOkHttpClient() - val retrofit = createRetrofit(okHttpClient) - return retrofit.create(VisaApi::class.java) - } - - private fun createOkHttpClient(): OkHttpClient { - val builder = OkHttpClient.Builder().apply { - connectTimeout(networkTimeoutSeconds, TimeUnit.SECONDS) - readTimeout(networkTimeoutSeconds, TimeUnit.SECONDS) - writeTimeout(networkTimeoutSeconds, TimeUnit.SECONDS) - - if (isNetworkLoggingEnabled) { - addInterceptor(createNetworkLoggingInterceptor()) - } - - if (headers.isNotEmpty()) { - addInterceptor { chain -> - val request = chain.request().newBuilder().apply { - headers.forEach { (key, value) -> addHeader(key, value) } - }.build() - - chain.proceed(request) - } - } - } - - return builder.build() - } - - private fun createRetrofit(okHttpClient: OkHttpClient): Retrofit { - val baseUrl = if (useDevApi) Constants.VISA_API_DEV_URL else Constants.VISA_API_PROD_URL - - return Retrofit.Builder() - .addConverterFactory(MoshiConverterFactory.create(moshi)) - .addCallAdapterFactory(ApiResponseCallAdapterFactory.create()) - .baseUrl(baseUrl) - .client(okHttpClient) - .build() - } -} - -private fun createNetworkLoggingInterceptor(): Interceptor { - return LoggingInterceptor.Builder() - .setLevel(Level.BODY) - .log(Log.VERBOSE) - .tag(NETWORK_LOGS_TAG) - .build() -} \ No newline at end of file diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/model/VisaContractInfo.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/model/VisaContractInfo.kt index e2e6f98d58..e50c6e3c89 100644 --- a/libs/visa/src/main/kotlin/com/tangem/lib/visa/model/VisaContractInfo.kt +++ b/libs/visa/src/main/kotlin/com/tangem/lib/visa/model/VisaContractInfo.kt @@ -25,7 +25,6 @@ data class VisaContractInfo( val available: Available, val blocked: BigDecimal, val debt: BigDecimal, - val pendingRefund: BigDecimal, ) { data class Available( diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/KotlinConfigurations.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/KotlinConfigurations.kt index d0e39d6777..ae7e54965a 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/KotlinConfigurations.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/KotlinConfigurations.kt @@ -1,7 +1,12 @@ package com.tangem.plugin.configuration.configurations import org.gradle.api.Project +import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension +import org.jetbrains.kotlin.gradle.model.KotlinAndroidExtension import org.jetbrains.kotlin.gradle.tasks.KotlinCompile internal fun Project.configureKotlinCompilerOptions() { diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt index fe2f2c147f..c19607fae8 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt @@ -2,9 +2,12 @@ package com.tangem.plugin.configuration.configurations.extension import com.android.build.gradle.BaseExtension import com.tangem.plugin.configuration.model.AppConfig +import com.tangem.plugin.configuration.utils.findPlugin import com.tangem.plugin.configuration.utils.findVersion import org.gradle.api.JavaVersion import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.plugins internal fun BaseExtension.configureCompileSdk() { compileSdkVersion(AppConfig.compileSdkVersion) @@ -30,10 +33,8 @@ internal fun BaseExtension.configureCompose(project: Project) { } buildFeatures.compose = useCompose - + if (useCompose) { - composeOptions { - kotlinCompilerExtensionVersion = project.findVersion(alias = "compose-compiler").requiredVersion - } + project.plugins.apply(project.findPlugin("kotlin-compose-compiler").pluginId) } } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 1ad6153ba0..cbc2e965dd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -106,6 +106,17 @@ dependencyResolutionManagement { includeGroupAndSubgroups("com.tangem.ic4j") } } + maven { + // setting any repository from tangem project allows maven search all packages in the project + url = uri("https://maven.pkg.github.com/tangem/web3j") + credentials { + username = properties.getProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = properties.getProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + content { + includeGroupAndSubgroups("org.web3j") + } + } maven("https://jitpack.io") } @@ -160,9 +171,10 @@ include(":features:onboarding") include(":features:onboarding-v2:api") include(":features:onboarding-v2:impl") +include(":features:referral:api") include(":features:referral:data") include(":features:referral:domain") -include(":features:referral:presentation") +include(":features:referral:impl") include(":features:swap:api") include(":features:swap:data") @@ -212,6 +224,15 @@ include(":features:onramp:impl") include(":features:stories:api") include(":features:stories:impl") + +include(":features:txhistory:api") +include(":features:txhistory:impl") + +include(":features:ask-biometry:api") +include(":features:ask-biometry:impl") + +include(":features:nft:api") +include(":features:nft:impl") // endregion Feature modules // region Domain modules diff --git a/version.properties b/version.properties deleted file mode 100644 index f5fd46675c..0000000000 --- a/version.properties +++ /dev/null @@ -1 +0,0 @@ -versionName=5.21.1 \ No newline at end of file