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 8cfba8dd64..67cd08e295 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..8c3b553cfe 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 @@ -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") } @@ -392,7 +394,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/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/DefaultExpressAuthProvider.kt b/app/src/main/java/com/tangem/tap/network/auth/DefaultExpressAuthProvider.kt index 7331b9f95c..452b90127d 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/DefaultExpressAuthProvider.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/DefaultExpressAuthProvider.kt @@ -4,6 +4,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getSyncOrDefault import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.wallets.models.UserWallet import com.tangem.lib.auth.ExpressAuthProvider import kotlinx.coroutines.runBlocking import java.util.UUID @@ -25,6 +26,17 @@ internal class DefaultExpressAuthProvider( } override fun getRefCode(): String { + val selectedUserWallet = userWalletsStore.selectedUserWalletOrNull ?: error("Can not get selected user wallet") + + return when { + isRing(selectedUserWallet) -> "ring" + isChangeNow(selectedUserWallet) -> "ChangeNow" + isPartner(selectedUserWallet) -> "partner" + else -> "" + } + } + + private fun isRing(selectedUserWallet: UserWallet): Boolean { val addedWalletsWithRings = runBlocking { appPreferencesStore.getSyncOrDefault( key = PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY, @@ -32,9 +44,19 @@ internal class DefaultExpressAuthProvider( ) } - val userWalletId = userWalletsStore.selectedUserWalletOrNull?.walletId?.stringValue - ?: error("No user id provided") + return addedWalletsWithRings.contains(selectedUserWallet.walletId.stringValue) + } - return if (addedWalletsWithRings.contains(userWalletId)) "ring" else "" + private fun isChangeNow(selectedUserWallet: UserWallet): Boolean { + return selectedUserWallet.scanResponse.card.batchId == BATCH_ID_CHANGENOW + } + + private fun isPartner(selectedUserWallet: UserWallet): Boolean { + return selectedUserWallet.scanResponse.card.batchId == BATCH_ID_PARTNER + } + + private companion object { + const val BATCH_ID_CHANGENOW = "BB000013" + const val BATCH_ID_PARTNER = "AF990015" } } \ No newline at end of file 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/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..07924cabd3 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.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.SaveWallet, - is AppRoute.Send, - is AppRoute.AppSettings, - is AppRoute.CardSettings, - is AppRoute.DetailsSecurity, - is AppRoute.Home, is AppRoute.OnboardingNote, 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..ce6d8603f5 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 @@ -68,15 +68,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 +84,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 +94,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 +124,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 +149,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 +169,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 +266,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..a7304f434c 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,21 @@ { "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" } ] 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/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/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-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 6edea905ea..003034b671 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -241,6 +241,7 @@ Dieser Mechanismus schützt vor Annäherungsangriffen auf eine Karte oder einen Ring. Er erzwingt eine Verzögerung zwischen dem Empfang und der Ausführung eines Befehls. Passcode Bevor du einen Befehl ausführst, der eine Änderung des Kartenstatus zur Folge hat, musst du den Passcode eingeben. + NFT Empfehlungsprogramm Flippe den Bildschirm deines Geräts nach unten, um Salden schnell ein- und auszublenden %s Hasch @@ -536,6 +537,7 @@ Möchtest du den Aktivierungsprozess abbrechen? Erste Schritte Für die Karte oder Ring, die du hinzufügen möchtest, wurde bereits eine andere Wallets erstellt. Wenn du Guthaben auf dieser Wallets hast, hebe es bitte ab, setze diese Karte oder Ring zurück und füge sie als Backup hinzu. + Sicher deine Wallet Backups anlegen Lese mehr über die Seed-Phrase @@ -668,6 +670,7 @@ Nutzung biometrischer Daten zulassen Bei Interaktionen mit deiner Wallet werden anstelle des Zugangscodes biometrische Daten abgefragt Zugangscode + Nicht zulassen Es sieht so aus, als ob du die biometrische Authentifizierung deaktiviert hast. Diese ist notwendig, um Wallets zu speichern Biometrische Autorisierung aktivieren Möchtest du Biometrie nutzen? diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index ada8a5bd4d..5b203d5b9a 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -241,6 +241,7 @@ Este mecanismo protege contra los ataques de proximidad a una tarjeta o anillo. Aplicará un retardo entre la recepción y la ejecución de un comando. Contraseña Antes de ejecutar un comando que cambie el estado de la tarjeta, deberá ingresar una contraseña. + NFT Programa de referidos Gire la pantalla de su dispositivo hacia abajo para ocultar y mostrar rápidamente los saldos %s hashes @@ -534,6 +535,7 @@ ¿Quiere salir del proceso de activación? Inicializando Ya se ha creado otra billetera en la tarjeta que está intentando agregar. Si tiene fondos en esta billetera, por favor retírelos y luego reinicie esta tarjeta y agréguela como backup. + Guardar su billetera Creando un backup Leer más sobre seed phrase @@ -666,6 +668,7 @@ Permitir el uso de biometría Se solicitará la biometría en lugar del código de acceso para interactuar con su billetera Código de acceso + No autorizar Parece que tiene la autenticación biométrica deshabilitada, es necesaria para guardar billeteras Activar la autorización biométrica ¿Le gustaría usar la biometría? diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 529de205c8..05074ae06e 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -241,6 +241,7 @@ Ce mécanisme protège contre les attaques sans contact sur la carte. Il y a un délai entre la réception et l\'exécution de la commande. Après la première transaction signée, ce téléphone sera associé à la carte et les transactions seront signées immédiatement. Mot de passe Avant d\'exécuter une commande qui modifie l\'état de la carte, vous devrez entrer un mot de passe. + NFT Programme de parrainage Retournez l\'écran de votre appareil vers le bas pour masquer et afficher rapidement les soldes %s hashes @@ -536,6 +537,7 @@ Voulez-vous quitter le processus d\'activation ? Initialisation en cours Un autre portefeuille a déjà été créé sur la carte que vous essayez d\'ajouter. Si vous avez des fonds dans ce portefeuille, veuillez les retirer, puis réinitialiser cette carte et l\'ajouter comme sauvegarde. + Enregistrer votre portefeuille Sauvegarde en cours En savoir plus sur les seed phrases @@ -668,6 +670,7 @@ Autoriser l\'utilisation de la biométrie La biométrie sera demandée à la place du code d\'accès pour les interactions avec votre portefeuille Code d\'accès + Ne pas autoriser Il semble que vous ayez désactivé l\'authentification biométrique, elle est nécessaire pour sauvegarder les portefeuilles Activer l\'autorisation biométrique Souhaitez-vous utiliser la biométrie ? diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index 900ef94aa5..9b25886a73 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -19,6 +19,7 @@ Questo meccanismo protegge dagli avvicinamenti senza contatto sulla carta. Attiva un ritardo tra la ricezione e l\'esecuzione di un comando. Dopo la prima transazione firmata, questo telefono verrà associato alla carta e le transazioni verranno firmate immediatamente. Password Dovrai inserire una password prima di eseguire qualsiasi comando che modifichi lo stato della carta. + NFT Hash %s ID carta Valuta dell\'applicazione diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index aedabcb589..6abf715762 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -238,6 +238,7 @@ このメカニズムは、カードまたはリングに対する近接攻撃から保護します。コマンドの受信と実行の間に遅延を強制します。 パスコード カードの状態の変更を伴うコマンドを実行する前に、パスコードを入力する必要があります。 + NFT 紹介プログラム デバイスの画面を下に向けると、残高をすばやく非表示にしたり表示したりできます。 %sハッシュ @@ -530,6 +531,7 @@ アクティベーション処理を途中で終了しますか? スタート 追加しようとしているカードには、すでに別のウォレットが作成されています。このウォレットに資金がある場合は、それを引き出してからこのカードをリセットし、バックアップとして追加してください。 + ウォレットを保存する バックアップの作成 シードフレーズについてもっと読む @@ -658,6 +660,7 @@ 生体認証の使用を許可する ウォレットの操作には、アクセスコードの代わりに生体認証が要求されます。 アクセスコード + 許可しない 生体認証が無効になっているようです。ウォレットを保存する必要があります。 生体認証を有効にする 生体認証を使用しますか? @@ -768,6 +771,7 @@ %s 推定利益 市場評価 指標 + %1$sネットワークルールによれば、 %2$sからの請求が可能です。以下の金額は、ステーキング解除時にアカウントに入金されます。 最低要件 報酬はありません 請求中の報酬 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 8793740014..33b0c3b4a9 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -247,6 +247,7 @@ Этот механизм защищает карту или кольцо от бесконтактных атак. Между сканированием и выполнением команды будет добавлена задержка. Пароль Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль. + NFT Реферальная программа Переверните экран вашего устройства вниз, чтобы быстро скрыть и отобразить балансы %s хэшей @@ -546,6 +547,7 @@ Вы хотите выйти из процесса активации? Подготовка Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Если на нем есть средства, пожалуйста сначала выведите их, а затем сделайте сброс до заводских настроек и используйте как резервную. + Сохраните ваш кошелек Резервная копия Прочитать о seed-фразе @@ -639,7 +641,7 @@ %1$s (%2$s) в сети %3$s Использование другой сети может привести к утрате средств. Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств. - Отправляйте только %1$s в сети %2$s + Отправляйте только %1$s в сети %2$s Переводите средства с любого кошелька или биржи Участвовать Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. @@ -686,6 +688,7 @@ Использовать биометрию Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты Код доступа + Не использовать Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков Включите биометрическую аутентификацию Вы хотите использовать биометрию? diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 91701bc5c1..214896573f 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -247,6 +247,7 @@ Цей механізм захищає картку або кільце від безконтактних атак. Між скануванням картки та виконанням команди буде додана затримка. Пароль Перед виконанням будь-якої команди, що тягне за собою зміну стану картки, вам необхідно буде ввести пароль. + NFT Реферальна програма Переверніть екран пристрою вниз, щоб швидко приховати та відобразити баланси %s хешів @@ -546,6 +547,7 @@ Ви хочете вийти з процесу активації? Підготовка На картці, яку ви намагаєтеся додати, вже створено інший гаманець. Якщо у вас є кошти на цьому гаманці, будь ласка, виведіть їх, а потім скиньте цю картку до заводських налаштувань і додайте її як резервну. + Збережіть ваш гаманець Резервна копія Дізнатися більше про seed-фразу @@ -686,6 +688,7 @@ Використовувати біометрію Для взаємодії з гаманцем будуть запитуватися біометричні дані замість коду доступу Код доступу + Не дозволяти Схоже, що у вас відключена біометрична автентифікація, вона необхідна для збереження гаманців Увімкніть біометричну автентифікацію Бажаєте використовувати біометрію? diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index aaa494dbeb..d4ab1bc4e2 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -108,6 +108,7 @@ 此機制可防止對卡進行鄰近攻擊。它將強制執行命令的接收和執行之間的延遲。 密碼 在執行任何需要更改卡狀態的命令之前,您必須輸入密碼 + NFT 推薦計畫 %s hashes 卡號 @@ -176,6 +177,7 @@ 這此情況,您必須要重新開始 您想要離開啟用程序嗎? 開始 + 儲存你的錢包 創建備份 閱讀更多關於助記詞的訊息 @@ -241,6 +243,7 @@ 允許使用生物辨識 將要求生物識別而不是訪問密碼來與您的錢包進行交互 訪問密碼 + 不允許 您好像禁用了生物識別,有必要保存錢包 啟用生物識別授權 您想使用生物識別技術嗎? diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 9b4f9524be..68238443cf 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -537,6 +537,7 @@ Do you want to exit the activation process? Getting started Another wallet has already been created on the card you\'re trying to add. If you have funds in this wallet, please withdraw it and then reset this card and add it as a backup. + Save your wallet Creating a backup Read more about seed phrase @@ -669,6 +670,7 @@ Allow to use biometrics Biometrics will be requested instead of the access code for interactions with your wallet Access code + Don\'t allow It looks like you have biometric authentication disabled, it is necessary to save wallets Enable biometric authorization Would you like to use biometrics? @@ -779,6 +781,7 @@ %s est. profit Market rating Metrics + According to %1$s network rules, claims are possible from %2$s. Amounts below will be credited to your account upon unstaking. Minimum Requirement No rewards available Reward claiming 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_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 8482f9a190..9a69615f74 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, @@ -638,13 +638,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") } } @@ -712,6 +713,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" @@ -734,6 +736,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/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/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..6381beb925 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, @@ -79,8 +80,14 @@ internal class OnboardingEntryModel @Inject constructor( } 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/ui/OnboardingEntry.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt index e9d2be883a..3ed12bd0e7 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 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..a176555bb7 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, 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/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 01d1f570c7..9ec36f1022 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, @@ -122,7 +119,7 @@ internal class StakingModel @Inject constructor( private val shareManager: ShareManager, @DelayedWork private val coroutineScope: CoroutineScope, private val innerRouter: InnerStakingRouter, - private val appRouter: AppRouter, + appRouter: AppRouter, ) : Model(), StakingClickIntents { val uiState: StateFlow = stateController.uiState @@ -242,7 +239,7 @@ internal class StakingModel @Inject constructor( return } isInitialInfoStep && noBalanceState -> { - stateController.update( + val list = buildList { SetConfirmationStateInitTransformer( isEnter = true, isExplicitExit = false, @@ -251,8 +248,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() @@ -400,7 +416,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, @@ -636,16 +652,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(), @@ -666,7 +672,6 @@ internal class StakingModel @Inject constructor( appCurrencyProvider = Provider { appCurrency }, feeCryptoCurrencyStatus = feeCryptoCurrencyStatus, currencyWarning = currencyWarning, - validatorError = validation, currencyCheck = currencyStatus, isSubtractAvailable = isAmountSubtractAvailable, feeError = feeError, @@ -715,7 +720,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(), @@ -930,7 +935,7 @@ internal class StakingModel @Inject constructor( when { isInitState() -> { updateInitialData() - balanceUpdater.initialUpdate() + balanceUpdater.partialUpdate() } isAssentState() -> { getFee() @@ -1024,7 +1029,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/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/helpers/StakingBalanceUpdater.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt index c84db7f3ea..6c26ecf208 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/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 c55fb7a485..dcc57f1a99 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 @@ -71,7 +78,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, @@ -81,7 +88,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 873626c210..e168a4db08 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,30 +1,33 @@ 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 import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary +import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.components.list.RoundedListWithDividersItemData import com.tangem.core.ui.extensions.* import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.percent -import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.staking.model.stakekit.BalanceItem import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.staking.impl.R -import com.tangem.features.staking.impl.presentation.state.* +import com.tangem.features.staking.impl.presentation.model.StakingClickIntents +import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState +import com.tangem.features.staking.impl.presentation.state.StakingStates +import com.tangem.features.staking.impl.presentation.state.StakingStep +import com.tangem.features.staking.impl.presentation.state.StakingUiState 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.features.staking.impl.presentation.model.StakingClickIntents -import com.tangem.lib.crypto.BlockchainUtils.isPolkadot import com.tangem.utils.Provider import com.tangem.utils.isNullOrZero import com.tangem.utils.transformer.Transformer @@ -145,7 +148,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) } @@ -239,6 +243,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 6aeb94cc74..c388dff746 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 @@ -212,15 +209,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( @@ -236,7 +224,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 89% 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 9b4d62818d..0aea80ff85 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 @@ -74,10 +73,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 @@ -88,9 +89,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, @@ -121,28 +123,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() @@ -194,17 +189,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 { @@ -213,36 +207,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, @@ -259,18 +241,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) { @@ -284,12 +265,12 @@ internal class TokenDetailsViewModel @Inject constructor( internalUiState.value = stateFactory.getManageButtonsState(actions = it.states) } .flowOn(dispatchers.main) - .launchIn(viewModelScope) + .launchIn(modelScope) .saveIn(buttonsJobHolder) } private fun updateWarnings(cryptoCurrencyStatus: CryptoCurrencyStatus) { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { getCurrencyWarningsUseCase.invoke( userWalletId = userWalletId, currencyStatus = cryptoCurrencyStatus, @@ -302,13 +283,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, @@ -326,13 +307,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() @@ -343,7 +324,7 @@ internal class TokenDetailsViewModel @Inject constructor( ::updateNetworkToSwapBalance, ) expressTxStatusTaskScheduler.scheduleTask( - viewModelScope, + modelScope, PeriodicTask( isDelayFirst = false, delay = EXPRESS_STATUS_UPDATE_DELAY, @@ -363,13 +344,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, @@ -382,29 +363,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) + } } } } @@ -436,12 +421,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 } @@ -461,7 +446,7 @@ internal class TokenDetailsViewModel @Inject constructor( maybeAppCurrency.getOrElse { AppCurrency.Default } } .stateIn( - scope = viewModelScope, + scope = modelScope, started = SharingStarted.Eagerly, initialValue = AppCurrency.Default, ) @@ -485,7 +470,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, @@ -497,7 +482,7 @@ internal class TokenDetailsViewModel @Inject constructor( } } else { showErrorIfDemoModeOrElse { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { reduxStateHolder.dispatch( TradeCryptoAction.Buy( userWallet = userWallet, @@ -563,7 +548,7 @@ internal class TokenDetailsViewModel @Inject constructor( return } - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonReceive(cryptoCurrency.symbol)) analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ReceiveScreenOpened(cryptoCurrency.symbol)) @@ -596,7 +581,7 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onGenerateExtendedKey() { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { val extendedKey = getExtendedPublicKeyForCurrencyUseCase( userWalletId, cryptoCurrency.network, @@ -670,7 +655,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) @@ -681,7 +666,7 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onHideConfirmed() { - viewModelScope.launch { + modelScope.launch { removeCurrencyUseCase.invoke(userWalletId, cryptoCurrency) .onLeft { Timber.e(it) } .onRight { router.popBackStack() } @@ -696,7 +681,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) @@ -728,7 +713,7 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onAddressTypeSelected(addressModel: AddressModel) { - viewModelScope.launch { + modelScope.launch { router.openUrl( url = getExploreUrlUseCase( userWalletId = userWalletId, @@ -753,7 +738,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( @@ -777,7 +762,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) } } @@ -807,7 +792,7 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onSwapPromoDismiss() { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { shouldShowSwapPromoTokenUseCase.neverToShow() analyticsEventsHandler.send( TokenSwapPromoAnalyticsEvent.PromotionBannerClicked( @@ -820,7 +805,7 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onSwapPromoClick() { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { shouldShowSwapPromoTokenUseCase.neverToShow() analyticsEventsHandler.send( TokenSwapPromoAnalyticsEvent.PromotionBannerClicked( @@ -851,7 +836,7 @@ internal class TokenDetailsViewModel @Inject constructor( blockchain = cryptoCurrency.network.name, ), ) - viewModelScope.launch { + modelScope.launch { retryIncompleteTransactionUseCase( userWalletId = userWalletId, currency = cryptoCurrency, @@ -892,13 +877,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, @@ -923,7 +908,7 @@ internal class TokenDetailsViewModel @Inject constructor( blockchain = cryptoCurrency.network.name, ), ) - viewModelScope.launch(dispatchers.io) { + modelScope.launch(dispatchers.io) { associateAssetUseCase( userWalletId = userWalletId, currency = cryptoCurrency, @@ -962,7 +947,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, @@ -994,7 +979,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 4a76d6f1c9..5ab5994819 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 @@ -26,7 +27,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( @@ -36,11 +38,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 { @@ -55,25 +59,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..cbf8bafda7 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,11 @@ internal class WalletSettingsModel @Inject constructor( isManageTokensAvailable = userWallet.isMultiCurrency, isRenameWalletAvailable = isRenameWalletAvailable, renameWallet = { openRenameWalletDialog(userWallet, dialogNavigation) }, + isNFTFeatureEnabled = isNFTFeatureEnabled, + isNFTEnabled = isNFTEnabled, + onCheckedNFTChange = { isChecked -> + onCheckedNFTChange(isChecked) + }, forgetWallet = { val message = DialogMessage( message = resourceReference(R.string.user_wallet_list_delete_prompt), @@ -151,4 +170,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 b9647e9365..157976d4f6 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 @@ -55,7 +56,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 @@ -101,7 +101,7 @@ interface WalletCurrencyActionsClickIntents { } @Suppress("LongParameterList", "LargeClass") -@ViewModelScoped +@ModelScoped internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, private val walletEventSender: WalletEventSender, @@ -205,7 +205,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, @@ -223,7 +223,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), @@ -270,7 +270,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 = { @@ -296,7 +296,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, @@ -329,7 +329,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( ) } else { showErrorIfDemoModeOrElse { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { reduxStateHolder.dispatch( TradeCryptoAction.Buy( userWallet = userWallet, @@ -368,7 +368,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( @@ -401,7 +401,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 @@ -436,7 +436,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( return } - viewModelScope.launch { + modelScope.launch { val swapRoute = getSwapRoute( AppRoute.SwapCrypto(userWalletId = userWalletId), ) @@ -463,7 +463,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) { @@ -509,7 +509,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( currency: CryptoCurrency, addressModel: AddressModel, ) { - viewModelScope.launch(dispatchers.main) { + modelScope.launch(dispatchers.main) { router.openUrl( url = getExploreUrlUseCase( userWalletId = userWalletId, @@ -543,7 +543,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( @@ -563,7 +563,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/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/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 60c5fb9778..0000000000 --- a/version.properties +++ /dev/null @@ -1 +0,0 @@ -versionName=5.21.0 \ No newline at end of file