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..e5a1e6a705 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
@@ -95,6 +102,8 @@ dependencies {
implementation(projects.domain.walletConnect)
implementation(projects.domain.markets)
implementation(projects.domain.manageTokens)
+ implementation(projects.domain.nft)
+ implementation(projects.domain.nft.models)
implementation(projects.domain.onramp)
implementation(projects.domain.promo)
implementation(projects.domain.promo.models)
@@ -137,11 +146,12 @@ dependencies {
implementation(projects.data.walletConnect)
implementation(projects.data.markets)
implementation(projects.data.manageTokens)
+ implementation(projects.data.nft)
implementation(projects.data.onramp)
/** 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 +189,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.biometry.api)
+ implementation(projects.features.biometry.impl)
+ implementation(projects.features.nft.api)
+ implementation(projects.features.nft.impl)
/** AndroidX libraries */
implementation(deps.androidx.core.ktx)
@@ -192,10 +208,12 @@ dependencies {
implementation(deps.androidx.paging.runtime)
implementation(deps.androidx.swipeRefreshLayout)
implementation(deps.androidx.fragment.compose)
+ implementation(deps.androidx.workmanager)
implementation(deps.lifecycle.runtime.ktx)
implementation(deps.lifecycle.common.java8)
implementation(deps.lifecycle.viewModel.ktx)
implementation(deps.lifecycle.compose)
+ implementation(deps.hilt.work)
/** Compose libraries */
implementation(deps.compose.constraintLayout)
@@ -234,6 +252,7 @@ dependencies {
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
+ kapt(deps.hilt.compilerx)
/** Other libraries */
implementation(deps.kotlin.immutable.collections)
@@ -263,7 +282,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/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1ebb3c801a..b9e46866c1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -27,8 +27,7 @@
-
+
@@ -145,6 +144,13 @@
+
+
+
+
@@ -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
@@ -370,7 +355,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
}
lockUserWalletsTimer = LockUserWalletsTimer(
- owner = this,
+ context = this,
settingsRepository = settingsRepository,
userWalletsListManager = userWalletsListManager,
coroutineScope = mainScope,
diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt
index 540e44d667..32e3b46f66 100644
--- a/app/src/main/java/com/tangem/tap/TangemApplication.kt
+++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt
@@ -1,6 +1,8 @@
package com.tangem.tap
import android.app.Application
+import androidx.hilt.work.HiltWorkerFactory
+import androidx.work.Configuration
import coil.ImageLoader
import coil.ImageLoaderFactory
import com.chuckerteam.chucker.api.ChuckerInterceptor
@@ -78,7 +80,7 @@ lateinit var store: Store
lateinit var foregroundActivityObserver: ForegroundActivityObserver
internal lateinit var derivationsFinder: DerivationsFinder
-abstract class TangemApplication : Application(), ImageLoaderFactory {
+abstract class TangemApplication : Application(), ImageLoaderFactory, Configuration.Provider {
// region DI
private val entryPoint: ApplicationEntryPoint
@@ -210,6 +212,14 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val uiMessageSender: UiMessageSender
get() = entryPoint.getUiMessageSender()
+ private val workerFactory: HiltWorkerFactory
+ get() = entryPoint.getWorkerFactory()
+
+ override val workManagerConfiguration: Configuration
+ get() = Configuration.Builder()
+ .setWorkerFactory(workerFactory)
+ .build()
+
// endregion
override fun onCreate() {
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt
index 217260e00b..fe2add9b71 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt
@@ -113,6 +113,7 @@ sealed class AnalyticsParam {
const val SEED_PHRASE_LENGTH = "Seed Phrase Length"
const val DAPP_NAME = "DApp Name"
const val DAPP_URL = "DApp Url"
+ const val NETWORKS = "Networks"
const val METHOD_NAME = "Method Name"
const val VALIDATION = "Validation"
const val BLOCKCHAIN_EXCEPTION_HOST = "exception_host"
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt
index d2870bbb6a..1725eabaaa 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt
@@ -1,6 +1,7 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.models.AnalyticsEvent
+import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType
/**
[REDACTED_AUTHOR]
@@ -12,8 +13,41 @@ internal sealed class WalletConnect(
) : AnalyticsEvent("Wallet Connect", event, params, error) {
class ScreenOpened : WalletConnect(event = "WC Screen Opened")
- class NewSessionEstablished(dAppName: String, dAppUrl: String, blockchainNames: List) : WalletConnect(
- event = "New Session Established",
+ class NewSessionInitiated(source: SourceType) : WalletConnect(
+ event = "Session Initiated",
+ params = mapOf(
+ AnalyticsParam.SOURCE to when (source) {
+ SourceType.QR -> "QR"
+ SourceType.DEEPLINK -> "DeepLink"
+ SourceType.ETC -> "etc"
+ },
+ ),
+ )
+
+ data object SessionFailed : WalletConnect(
+ event = "Session Failed",
+ )
+
+ class DAppConnectionRequested(
+ blockchainNames: List,
+ ) : WalletConnect(
+ event = "dApp Connection Requested",
+ params = mapOf(
+ AnalyticsParam.NETWORKS to blockchainNames.joinToString(","),
+ ),
+ )
+
+ class DAppConnected(dAppName: String, dAppUrl: String, blockchainNames: List) : WalletConnect(
+ event = "dApp Connected",
+ params = mapOf(
+ AnalyticsParam.DAPP_NAME to dAppName,
+ AnalyticsParam.DAPP_URL to dAppUrl,
+ AnalyticsParam.BLOCKCHAIN to blockchainNames.joinToString(","),
+ ),
+ )
+
+ class DAppConnectionFailed(dAppName: String, dAppUrl: String, blockchainNames: List) : WalletConnect(
+ event = "dApp Connection Failed",
params = mapOf(
AnalyticsParam.DAPP_NAME to dAppName,
AnalyticsParam.DAPP_URL to dAppUrl,
@@ -22,17 +56,31 @@ internal sealed class WalletConnect(
)
class SessionDisconnected(dAppName: String, dAppUrl: String) : WalletConnect(
- event = "Session Disconnected",
+ event = "dApp Disconnected",
params = mapOf(
AnalyticsParam.DAPP_NAME to dAppName,
AnalyticsParam.DAPP_URL to dAppUrl,
),
)
- class RequestHandled(
+ class SignatureRequestHandled(
params: RequestHandledParams,
) : WalletConnect(
- event = "Request Handled",
+ event = "Signature Request Handled",
+ params = params.toParamsMap(),
+ )
+
+ class SignatureRequestReceived(
+ params: RequestHandledParams,
+ ) : WalletConnect(
+ event = "Signature Request Received",
+ params = params.toParamsMap(),
+ )
+
+ class SignatureRequestFailed(
+ params: RequestHandledParams,
+ ) : WalletConnect(
+ event = "Signature Request Failed",
params = params.toParamsMap(),
)
diff --git a/app/src/main/java/com/tangem/tap/common/compose/TangemTextFieldsDefault.kt b/app/src/main/java/com/tangem/tap/common/compose/TangemTextFieldsDefault.kt
deleted file mode 100644
index 945ffc182f..0000000000
--- a/app/src/main/java/com/tangem/tap/common/compose/TangemTextFieldsDefault.kt
+++ /dev/null
@@ -1,152 +0,0 @@
-package com.tangem.tap.common.compose
-
-import androidx.compose.animation.animateColorAsState
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.interaction.InteractionSource
-import androidx.compose.foundation.interaction.collectIsFocusedAsState
-import androidx.compose.material.TextFieldColors
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.rememberUpdatedState
-import androidx.compose.ui.graphics.Color
-import com.tangem.core.ui.res.TangemTheme
-
-// Copy from com.tangem.core.ui.components
-// TODO: Delete this after fields has been moved to core-ui module
-internal object TangemTextFieldsDefault {
- val defaultTextFieldColors: TangemTextFieldColors
- @Composable @Stable get() = TangemTextFieldColors(
- textColor = TangemTheme.colors.text.primary1,
- disabledTextColor = TangemTheme.colors.text.disabled,
- backgroundColor = Color.Transparent,
- cursorColor = TangemTheme.colors.icon.primary1,
- errorCursorColor = TangemTheme.colors.icon.warning,
- focusedIndicatorColor = TangemTheme.colors.icon.primary1,
- unfocusedIndicatorColor = TangemTheme.colors.stroke.primary,
- disabledIndicatorColor = TangemTheme.colors.stroke.primary,
- errorIndicatorColor = TangemTheme.colors.icon.warning,
- leadingIconColor = TangemTheme.colors.icon.informative,
- disabledLeadingIconColor = Color.Transparent,
- errorLeadingIconColor = TangemTheme.colors.icon.warning,
- trailingIconColor = TangemTheme.colors.icon.informative,
- disabledTrailingIconColor = Color.Transparent,
- errorTrailingIconColor = TangemTheme.colors.icon.warning,
- focusedLabelColor = TangemTheme.colors.text.primary1,
- unfocusedLabelColor = TangemTheme.colors.text.secondary,
- disabledLabelColor = TangemTheme.colors.text.disabled,
- errorLabelColor = TangemTheme.colors.icon.warning,
- placeholderColor = TangemTheme.colors.text.secondary,
- disabledPlaceholderColor = TangemTheme.colors.text.disabled,
- captionColor = TangemTheme.colors.text.tertiary,
- disabledCaptionColor = TangemTheme.colors.text.disabled,
- errorCaptionColor = TangemTheme.colors.icon.warning,
- )
-}
-
-@Immutable
-internal data class TangemTextFieldColors(
- private val textColor: Color,
- private val disabledTextColor: Color,
- private val cursorColor: Color,
- private val errorCursorColor: Color,
- private val focusedIndicatorColor: Color,
- private val unfocusedIndicatorColor: Color,
- private val errorIndicatorColor: Color,
- private val disabledIndicatorColor: Color,
- private val leadingIconColor: Color,
- private val disabledLeadingIconColor: Color,
- private val errorLeadingIconColor: Color,
- private val trailingIconColor: Color,
- private val disabledTrailingIconColor: Color,
- private val errorTrailingIconColor: Color,
- private val backgroundColor: Color,
- private val focusedLabelColor: Color,
- private val unfocusedLabelColor: Color,
- private val disabledLabelColor: Color,
- private val errorLabelColor: Color,
- private val placeholderColor: Color,
- private val disabledPlaceholderColor: Color,
- private val captionColor: Color,
- private val disabledCaptionColor: Color,
- private val errorCaptionColor: Color,
-) : TextFieldColors {
-
- @Composable
- override fun leadingIconColor(enabled: Boolean, isError: Boolean): State {
- return rememberUpdatedState(
- when {
- !enabled -> disabledLeadingIconColor
- isError -> errorLeadingIconColor
- else -> leadingIconColor
- },
- )
- }
-
- @Composable
- override fun trailingIconColor(enabled: Boolean, isError: Boolean): State {
- return rememberUpdatedState(
- when {
- !enabled -> disabledTrailingIconColor
- isError -> errorTrailingIconColor
- else -> trailingIconColor
- },
- )
- }
-
- @Composable
- override fun indicatorColor(
- enabled: Boolean,
- isError: Boolean,
- interactionSource: InteractionSource,
- ): State {
- val focused by interactionSource.collectIsFocusedAsState()
-
- val targetValue = when {
- !enabled -> disabledIndicatorColor
- isError -> errorIndicatorColor
- focused -> focusedIndicatorColor
- else -> unfocusedIndicatorColor
- }
- return if (enabled) {
- animateColorAsState(targetValue, tween(durationMillis = 120))
- } else {
- rememberUpdatedState(targetValue)
- }
- }
-
- @Composable
- override fun backgroundColor(enabled: Boolean): State {
- return rememberUpdatedState(backgroundColor)
- }
-
- @Composable
- override fun placeholderColor(enabled: Boolean): State {
- return rememberUpdatedState(if (enabled) placeholderColor else disabledPlaceholderColor)
- }
-
- @Composable
- override fun labelColor(enabled: Boolean, error: Boolean, interactionSource: InteractionSource): State {
- val focused by interactionSource.collectIsFocusedAsState()
-
- val targetValue = when {
- !enabled -> disabledLabelColor
- error -> errorLabelColor
- focused -> focusedLabelColor
- else -> unfocusedLabelColor
- }
- return rememberUpdatedState(targetValue)
- }
-
- @Composable
- override fun textColor(enabled: Boolean): State {
- return rememberUpdatedState(if (enabled) textColor else disabledTextColor)
- }
-
- @Composable
- override fun cursorColor(isError: Boolean): State {
- return rememberUpdatedState(if (isError) errorCursorColor else cursorColor)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt b/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt
deleted file mode 100644
index aef0701282..0000000000
--- a/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.tangem.tap.common.extensions
-
-import android.view.View
-import android.widget.EditText
-import android.widget.TextSwitcher
-import android.widget.TextView
-import com.google.android.material.textfield.TextInputLayout
-
-/**
-[REDACTED_AUTHOR]
- */
-fun TextView.update(text: String?) {
- if (this.text?.toString() != text) this.text = text
-}
-
-fun EditText.update(text: String?) {
- if (this.text?.toString() == text) return
-
- val textLength = text?.length ?: 0
-
- // prevent cursor jumping while editing a text
- val cursorPosition = if (selectionEnd > textLength) textLength else selectionEnd
- this.setText(text)
- if (!isFocused || textLength == 0) return
-
- if (cursorPosition == 0) setSelection(textLength) else setSelection(cursorPosition)
-}
-
-fun EditText.setOnImeActionListener(action: Int, handler: (EditText) -> Unit) {
- this.setOnEditorActionListener { view, actionId, event ->
- if (actionId == action) {
- handler.invoke(this)
- return@setOnEditorActionListener true
- }
- return@setOnEditorActionListener false
- }
-}
-
-fun TextSwitcher.update(text: CharSequence?) {
- val textView = this.currentView as? TextView ?: return
-
- if (textView.text?.toString() != text?.toString()) this.setText(text)
-}
-
-// By default the TextInputLayout didn't activates the error state if the message is empty or null
-fun TextInputLayout.enableError(enable: Boolean, errorMessage: String? = null) {
- if (enable) {
- if (errorMessage == null || errorMessage.isEmpty()) {
- error = "Any message"
- if (childCount == 2) getChildAt(1).visibility = View.GONE
- } else {
- error = errorMessage
- }
- } else {
- error = null
- isErrorEnabled = false
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/di/RootAppComponentContextModule.kt b/app/src/main/java/com/tangem/tap/di/RootAppComponentContextModule.kt
index b59094d579..0d535ff71d 100644
--- a/app/src/main/java/com/tangem/tap/di/RootAppComponentContextModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/RootAppComponentContextModule.kt
@@ -5,7 +5,7 @@ import androidx.appcompat.app.AppCompatActivity
import com.arkivanov.decompose.defaultComponentContext
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.DefaultAppComponentContext
-import com.tangem.core.decompose.di.DecomposeComponent
+import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.di.RootAppComponentContext
import com.tangem.core.decompose.ui.UiMessageSender
@@ -27,7 +27,7 @@ internal object RootAppComponentContextModule {
fun provideRootAppComponentContext(
@ActivityContext context: Context,
dispatchers: CoroutineDispatcherProvider,
- componentBuilder: DecomposeComponent.Builder,
+ componentBuilder: ModelComponent.Builder,
@GlobalUiMessageSender messageSender: UiMessageSender,
): AppComponentContext {
return DefaultAppComponentContext(
diff --git a/app/src/main/java/com/tangem/tap/di/domain/AnalyticsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/AnalyticsDomainModule.kt
index dda9f2c253..8e89c073b8 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/AnalyticsDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/AnalyticsDomainModule.kt
@@ -5,10 +5,10 @@ import com.tangem.domain.analytics.repository.AnalyticsRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
-import dagger.hilt.android.components.ViewModelComponent
+import dagger.hilt.components.SingletonComponent
@Module
-@InstallIn(ViewModelComponent::class)
+@InstallIn(SingletonComponent::class)
internal object AnalyticsDomainModule {
@Provides
diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt
index 7f5f095079..dffd82ec11 100644
--- a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt
@@ -7,6 +7,8 @@ import com.tangem.blockchain.common.Wallet
import com.tangem.common.CompletionResult
import com.tangem.domain.card.models.TwinKey
import com.tangem.domain.models.scan.isRing
+import com.tangem.operations.sign.SignData
+import com.tangem.tap.domain.tasks.MultiSignHashTask
import com.tangem.tap.domain.tasks.SignHashesTask
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
@@ -64,6 +66,40 @@ class TangemSigner(
is CompletionResult.Failure -> CompletionResult.Failure(result.error)
}
}
+
+ override suspend fun multiSign(
+ dataToSign: List,
+ publicKey: Wallet.PublicKey,
+ ): CompletionResult