Updated on 2026-08-14
This commit is contained in:
commit
2da48c037a
961 changed files with 24212 additions and 13399 deletions
|
|
@ -1,40 +0,0 @@
|
|||
package com.tangem.tap.common
|
||||
|
||||
import timber.log.Timber
|
||||
|
||||
class CompositionCounter(
|
||||
val id: String,
|
||||
count: Int = 0,
|
||||
) {
|
||||
var count: Int = count
|
||||
private set
|
||||
|
||||
fun increase(id: String): CompositionCounter {
|
||||
if (this.id != id) return this
|
||||
|
||||
count += 1
|
||||
return CompositionCounter(id, count)
|
||||
}
|
||||
}
|
||||
|
||||
class CompositionLogger(
|
||||
private val recomposeViewId: String,
|
||||
private val tag: String = recomposeViewId,
|
||||
private var turnOnForIds: List<String> = listOf(recomposeViewId),
|
||||
) {
|
||||
val count: Int
|
||||
get() = compositionCounter.count
|
||||
|
||||
private var compositionCounter: CompositionCounter = CompositionCounter(recomposeViewId)
|
||||
|
||||
fun nextComposition() {
|
||||
compositionCounter = compositionCounter.increase(recomposeViewId)
|
||||
log("")
|
||||
}
|
||||
|
||||
fun log(message: String) {
|
||||
if (!turnOnForIds.contains(recomposeViewId)) return
|
||||
|
||||
Timber.d("$tag[$recomposeViewId]:[${compositionCounter.count}]: $message")
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,9 @@ import android.content.Context
|
|||
import android.net.Uri
|
||||
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_DARK
|
||||
import androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_LIGHT
|
||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||
import com.tangem.tap.common.extensions.getColorCompat
|
||||
import com.tangem.wallet.R
|
||||
|
||||
|
|
@ -15,6 +18,9 @@ class CustomTabsManager {
|
|||
.setNavigationBarColor(context.getColorCompat(R.color.toolbarColor))
|
||||
.build(),
|
||||
)
|
||||
.setColorScheme(
|
||||
if (MutableAppThemeModeHolder.isDarkThemeActive) COLOR_SCHEME_DARK else COLOR_SCHEME_LIGHT,
|
||||
)
|
||||
.build()
|
||||
customTabsIntent.launchUrl(context, Uri.parse(url))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.analytics.ChangeCardAnalyticsContextUseCase
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
|
||||
internal class DefaultChangeCardAnalyticsContextUseCase : ChangeCardAnalyticsContextUseCase {
|
||||
|
||||
override fun invoke(scanResponse: ScanResponse) {
|
||||
Analytics.setContext(scanResponse)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,10 +8,7 @@ sealed class AnalyticsParam {
|
|||
class Currency(currency: com.tangem.tap.features.wallet.models.Currency) : CurrencyType(currency.currencySymbol)
|
||||
class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency)
|
||||
class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol)
|
||||
class FiatCurrency(
|
||||
fiatCurrency: com.tangem.tap.common.entities.FiatCurrency,
|
||||
) : CurrencyType(fiatCurrency.code)
|
||||
|
||||
class FiatCurrency(fiatCurrency: com.tangem.tap.common.entities.FiatCurrency) : CurrencyType(fiatCurrency.code)
|
||||
class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol)
|
||||
}
|
||||
|
||||
|
|
@ -136,9 +133,9 @@ sealed class AnalyticsParam {
|
|||
}
|
||||
|
||||
sealed class WalletCreationType(val value: String) {
|
||||
object PrivateKey : WalletCreationType("Private key")
|
||||
object NewSeed : WalletCreationType("New seed")
|
||||
object SeedImport : WalletCreationType("Seed import")
|
||||
object PrivateKey : WalletCreationType(value = "Private Key")
|
||||
object NewSeed : WalletCreationType(value = "New Seed")
|
||||
object SeedImport : WalletCreationType(value = "Seed Import")
|
||||
}
|
||||
|
||||
companion object Key {
|
||||
|
|
@ -155,7 +152,8 @@ sealed class AnalyticsParam {
|
|||
const val ERROR_DESCRIPTION = "Error Description"
|
||||
const val ERROR_CODE = "Error Code"
|
||||
const val ERROR_KEY = "Error Key"
|
||||
const val CREATION_TYPE = "Creation type"
|
||||
const val CREATION_TYPE = "Creation Type"
|
||||
const val SEED_PHRASE_LENGTH = "Seed Phrase Length"
|
||||
const val DAPP_NAME = "DApp Name"
|
||||
const val DAPP_URL = "DApp Url"
|
||||
const val METHOD_NAME = "Method Name"
|
||||
|
|
|
|||
|
|
@ -32,14 +32,18 @@ sealed class Basic(
|
|||
batch: String,
|
||||
signInType: SignInType,
|
||||
walletsCount: String,
|
||||
hasBackup: Boolean?,
|
||||
) : Basic(
|
||||
event = "Signed in",
|
||||
params = mapOf(
|
||||
AnalyticsParam.CURRENCY to currency.value,
|
||||
AnalyticsParam.BATCH to batch,
|
||||
"Sign in type" to signInType.name,
|
||||
"Wallets Count" to walletsCount,
|
||||
),
|
||||
params = buildMap {
|
||||
put(AnalyticsParam.CURRENCY, currency.value)
|
||||
put(AnalyticsParam.BATCH, batch)
|
||||
put("Sign in type", signInType.name)
|
||||
put("Wallets Count", walletsCount)
|
||||
if (hasBackup != null) {
|
||||
put("Backuped", if (hasBackup) "Yes" else "No")
|
||||
}
|
||||
},
|
||||
) {
|
||||
enum class SignInType {
|
||||
Card, Biometric
|
||||
|
|
|
|||
|
|
@ -23,9 +23,14 @@ sealed class Onboarding(
|
|||
class ButtonCreateWallet : CreateWallet("Button - Create Wallet")
|
||||
class WalletCreatedSuccessfully(
|
||||
creationType: AnalyticsParam.WalletCreationType = AnalyticsParam.WalletCreationType.PrivateKey,
|
||||
seedPhraseLength: Int? = null,
|
||||
) : CreateWallet(
|
||||
event = "Wallet Created Successfully",
|
||||
params = mapOf(AnalyticsParam.CREATION_TYPE to creationType.value),
|
||||
params = buildMap {
|
||||
put(AnalyticsParam.CREATION_TYPE, creationType.value)
|
||||
|
||||
if (seedPhraseLength != null) put(AnalyticsParam.SEED_PHRASE_LENGTH, seedPhraseLength.toString())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ sealed class Portfolio(
|
|||
) : AnalyticsEvent("Portfolio", event, params) {
|
||||
|
||||
class Refreshed : Portfolio("Refreshed")
|
||||
|
||||
class ButtonManageTokens : Portfolio("Button - Manage Tokens")
|
||||
|
||||
class TokenTapped : Portfolio("Token is Tapped")
|
||||
|
||||
class OrganizeTokens : Portfolio("Button - Organize Tokens")
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ sealed class Settings(
|
|||
class ButtonAppSettings : Settings(event = "Button - App Settings")
|
||||
class ButtonCreateBackup : Settings(event = "Button - Create Backup")
|
||||
class ButtonWalletConnect : Settings(event = "Button - Wallet Connect")
|
||||
object ScanNewCard : Settings(event = "Button - Scan New Card")
|
||||
|
||||
class ButtonSocialNetwork(network: SocialNetwork) : Settings(
|
||||
event = "Button - Social Network",
|
||||
|
|
@ -78,5 +79,10 @@ sealed class Settings(
|
|||
)
|
||||
|
||||
object ButtonEnableBiometricAuthentication : AppSettings(event = "Button - Enable Biometric Authentication")
|
||||
|
||||
class MainCurrencyChanged(currencyType: String) : MainScreen(
|
||||
event = "Main Currency Changed",
|
||||
params = mapOf("Currency Type" to currencyType),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,4 +14,6 @@ internal object MutableAppThemeModeHolder : AppThemeModeHolder {
|
|||
appThemeMode.value = value
|
||||
}
|
||||
get() = appThemeMode.value
|
||||
|
||||
var isDarkThemeActive: Boolean = false
|
||||
}
|
||||
|
|
@ -1,277 +0,0 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
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.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.LinearProgressIndicator
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextFieldColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.common.module.ModuleError
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.tap.common.CompositionLogger
|
||||
import com.tangem.tap.common.compose.extensions.stringResourceDefault
|
||||
import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun OutlinedTextFieldWidget(
|
||||
fieldData: Field.Data<String>,
|
||||
labelId: Int? = null,
|
||||
label: String = "",
|
||||
placeholderId: Int? = null,
|
||||
placeholder: String = "",
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
isEnabled: Boolean = true,
|
||||
isVisible: Boolean = true,
|
||||
isLoading: Boolean = false,
|
||||
error: ModuleError? = null,
|
||||
errorConverter: ModuleMessageConverter? = null,
|
||||
debounceTextChanges: Long = 400,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
onTextChange: (String) -> Unit,
|
||||
) {
|
||||
if (!isVisible) return
|
||||
|
||||
Column(modifier = Modifier.animateContentSize()) {
|
||||
OutlinedProgressTextField(
|
||||
fieldData = fieldData,
|
||||
label = stringResourceDefault(labelId, label),
|
||||
placeholder = stringResourceDefault(placeholderId, placeholder),
|
||||
trailingIcon = trailingIcon,
|
||||
isEnabled = isEnabled,
|
||||
isLoading = isLoading,
|
||||
error = error,
|
||||
debounce = debounceTextChanges,
|
||||
visualTransformation = visualTransformation,
|
||||
keyboardOptions = keyboardOptions,
|
||||
onTextChange = onTextChange,
|
||||
)
|
||||
errorConverter?.let { AnimatedErrorView(errorConverter = it, error = error) }
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "NestedBlockDepth", "MagicNumber", "MaxLineLength")
|
||||
@Composable
|
||||
private fun OutlinedProgressTextField(
|
||||
fieldData: Field.Data<String>,
|
||||
label: String = "",
|
||||
placeholder: String = "",
|
||||
isEnabled: Boolean = true,
|
||||
isLoading: Boolean = false,
|
||||
error: ModuleError? = null,
|
||||
debounce: Long = 400,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
colors: TextFieldColors = TangemTextFieldsDefault.defaultTextFieldColors,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
onTextChange: (String) -> Unit,
|
||||
) {
|
||||
val logger = remember {
|
||||
CompositionLogger(label, "OutlinedProgressTextField", listOf("Символ токена"))
|
||||
}
|
||||
logger.nextComposition()
|
||||
|
||||
val textValueState = remember { mutableStateOf(fieldData.value) }
|
||||
val textDebouncer = valueDebouncerAsState(
|
||||
initialValue = fieldData.value,
|
||||
debounce = debounce,
|
||||
onEmitValueReceive = {
|
||||
logger.log("DEBOUNCER: onEmitValueReceived: [$it]")
|
||||
logger.log("DEBOUNCER: start RECOMPOSE by new value for textValueState.value = [$it]")
|
||||
textValueState.value = it
|
||||
},
|
||||
onValueChange = {
|
||||
logger.log("DEBOUNCER: onValueChanged: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> dispatch.toStore([$it])")
|
||||
onTextChange(it)
|
||||
},
|
||||
)
|
||||
|
||||
logger.log("RECOMPOSE ---------------------------------------------------------------START [${logger.count}]")
|
||||
logger.log("RECOMPOSE --data: fieldData.value: [$fieldData]")
|
||||
logger.log("RECOMPOSE --data: textValueState.value: [${textValueState.value}]")
|
||||
logger.log("RECOMPOSE --data: textDebouncer.emittedValue = [${textDebouncer.emittedValue}]")
|
||||
logger.log("RECOMPOSE --data: textDebouncer.debounced = [${textDebouncer.debounced}]")
|
||||
|
||||
if (!fieldData.isUserInput) {
|
||||
// initial value is not from an user
|
||||
val isNotUserInput = "-- IS NOT USER INPUT"
|
||||
logger.log("recompose $isNotUserInput")
|
||||
if (textValueState.value == fieldData.value) {
|
||||
logger.log("$isNotUserInput: внешние данные ОДИНАКОВЫ с данными в поле")
|
||||
} else {
|
||||
logger.log("$isNotUserInput: внешние данные РАЗЛИЧАЮТСЯ с данными в поле")
|
||||
if (textDebouncer.emittedValue != textDebouncer.debounced || textDebouncer.emitsCountBeforeDebounce > 0) {
|
||||
logger.log("$isNotUserInput: пользователь ВВОДИТ данные -> внешние данные игнорируем, ждем RECOMPOSE")
|
||||
} else {
|
||||
logger.log("$isNotUserInput: пользователь НЕ вводит данные -> пытаемся обработать внешние данные")
|
||||
if (textValueState.value != textDebouncer.emittedValue ||
|
||||
textValueState.value != textDebouncer.debounced
|
||||
) {
|
||||
logger.log("$isNotUserInput: даннные в поле не соответствуют данным из textDebouncer")
|
||||
if (textDebouncer.emittedValue.isEmpty() && textDebouncer.debounced.isEmpty()) {
|
||||
logger.log(
|
||||
"$isNotUserInput: даннные в textDebouncer ПУСТЫ -> start RECOMPOSE новые данные для " +
|
||||
"textValueState.value = [${fieldData.value}]",
|
||||
)
|
||||
textValueState.value = fieldData.value
|
||||
} else {
|
||||
logger.log(
|
||||
"$isNotUserInput: даннные в textDebouncer НЕ ПУСТЫ -> start RECOMPOSE новые данные для " +
|
||||
"textValueState.value = [${fieldData.value}]",
|
||||
)
|
||||
textValueState.value = fieldData.value
|
||||
}
|
||||
} else {
|
||||
logger.log(
|
||||
"$isNotUserInput: в пустое поле вставляются данные -> start RECOMPOSE новые данные для " +
|
||||
"textValueState.value = [${fieldData.value}]",
|
||||
)
|
||||
textValueState.value = fieldData.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.log("recompose --------------------------------------------------------------FINISH [${logger.count}]")
|
||||
|
||||
Box {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
value = textValueState.value,
|
||||
onValueChange = {
|
||||
logger.log("WIDGET: textDebouncer.emmit([$it])")
|
||||
textDebouncer.emmit(it)
|
||||
},
|
||||
keyboardOptions = keyboardOptions,
|
||||
label = {
|
||||
Text(
|
||||
text = label,
|
||||
style = TangemTheme.typography.caption,
|
||||
color = colors.labelColor(
|
||||
enabled = isEnabled,
|
||||
error = error != null,
|
||||
interactionSource = interactionSource,
|
||||
).value,
|
||||
)
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = placeholder,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = colors.placeholderColor(enabled = isEnabled).value,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
trailingIcon = trailingIcon,
|
||||
singleLine = true,
|
||||
enabled = isEnabled,
|
||||
isError = error != null,
|
||||
visualTransformation = visualTransformation,
|
||||
colors = colors,
|
||||
interactionSource = interactionSource,
|
||||
)
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(start = 6.dp, top = 0.dp, end = 6.dp, bottom = 6.dp),
|
||||
visible = isLoading,
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
color = TangemTheme.colors.icon.primary1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AnimatedErrorView(errorConverter: ModuleMessageConverter, error: ModuleError? = null) {
|
||||
AnimatedVisibility(
|
||||
visible = error != null,
|
||||
enter = fadeIn() + slideInVertically(),
|
||||
exit = slideOutVertically() + fadeOut(),
|
||||
) {
|
||||
error?.let {
|
||||
ErrorView(
|
||||
text = errorConverter.convert(it).message,
|
||||
style = TextStyle(fontSize = 14.sp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun OutlinedTextFieldWithErrorTest() {
|
||||
val context = LocalContext.current
|
||||
val converter = remember { ModuleMessageConverter(context) }
|
||||
|
||||
class SimpleError(
|
||||
override val code: Int = 1,
|
||||
override val message: String = "Error message",
|
||||
override val data: Any? = null,
|
||||
) : ModuleError()
|
||||
|
||||
val modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
Column {
|
||||
OutlinedTextFieldWidget(
|
||||
fieldData = Field.Data("", false),
|
||||
label = "First label",
|
||||
placeholder = "1 placeholder",
|
||||
error = null,
|
||||
errorConverter = converter,
|
||||
) {}
|
||||
OutlinedTextFieldWidget(
|
||||
fieldData = Field.Data("First", false),
|
||||
label = "First label",
|
||||
placeholder = "1 placeholder",
|
||||
error = null,
|
||||
errorConverter = converter,
|
||||
) {}
|
||||
OutlinedTextFieldWidget(
|
||||
fieldData = Field.Data("First", false),
|
||||
label = "First label",
|
||||
placeholder = "1 placeholder",
|
||||
isLoading = true,
|
||||
error = null,
|
||||
errorConverter = converter,
|
||||
) {}
|
||||
OutlinedTextFieldWidget(
|
||||
fieldData = Field.Data("First", false),
|
||||
label = "First label",
|
||||
placeholder = "1 placeholder",
|
||||
error = SimpleError(),
|
||||
errorConverter = converter,
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
* Compose views are not typically used as a main or base view.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun TitleSubtitle(title: String, subtitle: String) {
|
||||
Column {
|
||||
Text(text = title)
|
||||
Text(
|
||||
text = subtitle,
|
||||
fontSize = 12.sp,
|
||||
color = Color.Gray,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun stringResourceDefault(@StringRes id: Int?, default: String = ""): String {
|
||||
val resources = LocalContext.current.resources
|
||||
return try {
|
||||
resources.getString(requireNotNull(id))
|
||||
} catch (ex: Resources.NotFoundException) {
|
||||
default
|
||||
} catch (ex: IllegalArgumentException) {
|
||||
default
|
||||
}
|
||||
}
|
||||
|
|
@ -47,6 +47,7 @@ fun Blockchain.getGreyedOutIconRes(): Int {
|
|||
Blockchain.AlephZero, Blockchain.AlephZeroTestnet -> R.drawable.ic_azero_no_color
|
||||
Blockchain.OctaSpace, Blockchain.OctaSpaceTestnet -> R.drawable.ic_octaspace_no_color
|
||||
Blockchain.Chia, Blockchain.ChiaTestnet -> R.drawable.ic_chia_no_color
|
||||
Blockchain.Near, Blockchain.NearTestnet -> R.drawable.ic_near_no_color
|
||||
else -> R.drawable.ic_tangem_logo
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import com.tangem.core.navigation.AppScreen
|
|||
import com.tangem.core.navigation.FragmentShareTransition
|
||||
import com.tangem.feature.referral.ReferralFragment
|
||||
import com.tangem.feature.swap.presentation.SwapFragment
|
||||
import com.tangem.tap.features.customtoken.legacy.AddCustomTokenFragment
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.AddCustomTokenFragment
|
||||
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
|
||||
|
|
@ -33,7 +34,6 @@ import com.tangem.tap.proxy.redux.DaggerGraphState
|
|||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import timber.log.Timber
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.AddCustomTokenFragment as RedesignedAddCustomTokenFragment
|
||||
|
||||
fun FragmentActivity.openFragment(
|
||||
screen: AppScreen,
|
||||
|
|
@ -155,21 +155,10 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
|
|||
AppScreen.AccessCodeRecovery -> AccessCodeRecoveryFragment()
|
||||
AppScreen.Disclaimer -> DisclaimerFragment()
|
||||
AppScreen.AddTokens -> TokensListFragment()
|
||||
|
||||
AppScreen.AddCustomToken -> {
|
||||
val featureToggles = store.state.daggerGraphState.get(
|
||||
getDependency = DaggerGraphState::customTokenFeatureToggles,
|
||||
)
|
||||
if (featureToggles.isRedesignedScreenEnabled) {
|
||||
RedesignedAddCustomTokenFragment()
|
||||
} else {
|
||||
AddCustomTokenFragment()
|
||||
}
|
||||
}
|
||||
|
||||
AppScreen.AddCustomToken -> AddCustomTokenFragment()
|
||||
AppScreen.WalletDetails -> {
|
||||
val featureToggles = store.state.daggerGraphState.get(
|
||||
getDependency = DaggerGraphState::tokenDetailsFeatureToggles,
|
||||
getDependency = DaggerGraphState::walletFeatureToggles,
|
||||
)
|
||||
if (featureToggles.isRedesignedScreenEnabled) {
|
||||
store.state.daggerGraphState
|
||||
|
|
@ -186,5 +175,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
|
|||
AppScreen.Welcome -> WelcomeFragment()
|
||||
AppScreen.SaveWallet -> SaveWalletBottomSheetFragment()
|
||||
AppScreen.WalletSelector -> WalletSelectorBottomSheetFragment()
|
||||
AppScreen.AppCurrencySelector -> AppCurrencySelectorFragment()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
typealias ValueCallback<T> = (T) -> Unit
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.extensions.amountToCreateAccount
|
||||
import com.tangem.tap.common.TestActions
|
||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
|
|
@ -71,6 +71,7 @@ fun WalletManager.getTopUpUrl(): String? {
|
|||
cryptoCurrencyName = wallet.blockchain.currency,
|
||||
fiatCurrencyName = globalState.appCurrency.code,
|
||||
walletAddress = defaultAddress,
|
||||
isDarkTheme = MutableAppThemeModeHolder.isDarkThemeActive,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -79,12 +80,4 @@ fun WalletManager?.getAddressData(): WalletDataModel.AddressData? {
|
|||
|
||||
val addressDataList = wallet.createAddressesData()
|
||||
return if (addressDataList.isEmpty()) null else addressDataList[0]
|
||||
}
|
||||
|
||||
fun <T> WalletManager.Companion.stub(): T {
|
||||
val wallet = Wallet(Blockchain.Unknown, setOf(), Wallet.PublicKey(byteArrayOf(), null), setOf())
|
||||
return object : WalletManager(wallet) {
|
||||
override val currentHost: String = ""
|
||||
override suspend fun update() {}
|
||||
} as T
|
||||
}
|
||||
|
|
@ -7,8 +7,10 @@ import com.tangem.domain.models.scan.CardDTO
|
|||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.userwallets.UserWalletIdBuilder
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
class AdditionalFeedbackInfo {
|
||||
|
||||
class EmailWalletInfo(
|
||||
var blockchain: Blockchain = Blockchain.Unknown,
|
||||
var derivationPath: String = "",
|
||||
|
|
@ -46,6 +48,7 @@ class AdditionalFeedbackInfo {
|
|||
private val Address.name: String
|
||||
get() = type.javaClass.simpleName
|
||||
|
||||
@Deprecated("Don't use it directly")
|
||||
fun setCardInfo(data: ScanResponse) {
|
||||
cardId = data.card.cardId
|
||||
cardBlockchain = data.walletData?.blockchain ?: ""
|
||||
|
|
@ -55,6 +58,7 @@ class AdditionalFeedbackInfo {
|
|||
userWalletId = UserWalletIdBuilder.scanResponse(data).build()?.stringValue ?: ""
|
||||
}
|
||||
|
||||
@Deprecated("Don't use it directly")
|
||||
fun setWalletsInfo(walletManagers: List<WalletManager>) {
|
||||
walletsInfo.clear()
|
||||
tokens.clear()
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.redux.domainStore
|
|||
import com.tangem.domain.redux.global.NetworkServices
|
||||
import com.tangem.tap.common.redux.global.GlobalMiddleware
|
||||
import com.tangem.tap.common.redux.global.GlobalState
|
||||
import com.tangem.tap.common.redux.legacy.LegacyMiddleware
|
||||
import com.tangem.tap.common.redux.navigation.navigationMiddleware
|
||||
import com.tangem.tap.features.details.redux.DetailsMiddleware
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
|
|
@ -68,7 +69,7 @@ data class AppState(
|
|||
val daggerGraphState: DaggerGraphState = DaggerGraphState(),
|
||||
) : StateType {
|
||||
|
||||
val domainState: DomainState
|
||||
private val domainState: DomainState
|
||||
get() = domainStore.state
|
||||
|
||||
val domainNetworks: NetworkServices
|
||||
|
|
@ -107,6 +108,7 @@ data class AppState(
|
|||
AccessCodeRequestPolicyMiddleware().middleware,
|
||||
SignInMiddleware.middleware,
|
||||
DaggerGraphMiddleware.daggerGraphMiddleware,
|
||||
LegacyMiddleware.legacyMiddleware,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
package com.tangem.tap.common.redux.global
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.config.models.ChatConfig
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.tap.common.analytics.topup.TopUpController
|
||||
|
|
@ -48,13 +47,6 @@ sealed class GlobalAction : Action {
|
|||
object Stop : Onboarding()
|
||||
}
|
||||
|
||||
data class ScanCard(
|
||||
val additionalBlockchainsToDerive: Collection<Blockchain>? = null,
|
||||
val onSuccess: ((ScanResponse) -> Unit)? = null,
|
||||
val onFailure: ((TangemError) -> Unit)? = null,
|
||||
val messageResId: Int? = null,
|
||||
) : GlobalAction()
|
||||
|
||||
object ScanFailsCounter {
|
||||
data class ChooseBehavior(val result: CompletionResult<ScanResponse>) : GlobalAction()
|
||||
object Reset : GlobalAction()
|
||||
|
|
@ -103,4 +95,5 @@ sealed class GlobalAction : Action {
|
|||
}
|
||||
|
||||
data class UpdateUserWalletsListManager(val manager: UserWalletsListManager) : GlobalAction()
|
||||
data class ChangeAppThemeMode(val appThemeMode: AppThemeMode) : GlobalAction()
|
||||
}
|
||||
|
|
@ -3,12 +3,12 @@ package com.tangem.tap.common.redux.global
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
|
|
@ -16,7 +16,6 @@ import com.tangem.tap.common.extensions.dispatchOnMain
|
|||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.features.send.redux.SendAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.exchangeServices.BuyExchangeService
|
||||
|
|
@ -26,8 +25,13 @@ import com.tangem.tap.network.exchangeServices.ExchangeService
|
|||
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoEnvironment
|
||||
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService
|
||||
import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.walletCurrenciesManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.DispatchFunction
|
||||
|
|
@ -66,7 +70,13 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
|
|||
}
|
||||
}
|
||||
is GlobalAction.RestoreAppCurrency -> {
|
||||
if (store.state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles).isRedesignedScreenEnabled) {
|
||||
val daggerGraphState = store.state.daggerGraphState
|
||||
val walletFeatureToggles = daggerGraphState.get(DaggerGraphState::walletFeatureToggles)
|
||||
val detailsFeatureToggles = daggerGraphState.get(DaggerGraphState::detailsFeatureToggles)
|
||||
|
||||
if (walletFeatureToggles.isRedesignedScreenEnabled ||
|
||||
detailsFeatureToggles.isRedesignedAppCurrencySelectorEnabled
|
||||
) {
|
||||
restoreAppCurrencyNew()
|
||||
} else {
|
||||
restoreAppCurrencyLegacy()
|
||||
|
|
@ -75,10 +85,10 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
|
|||
is GlobalAction.HideWarningMessage -> {
|
||||
store.state.globalState.warningManager?.let {
|
||||
if (it.hideWarning(action.warning)) {
|
||||
if (WarningMessagesManager.isAlreadySignedHashesWarning(action.warning)) {
|
||||
// TODO: No appropriate warningMessage identification. Make it better later
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
}
|
||||
// if (WarningMessagesManager.isAlreadySignedHashesWarning()) {
|
||||
// // TODO: No appropriate warningMessage identification. Make it better later
|
||||
// store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
// }
|
||||
|
||||
store.dispatch(WalletAction.Warnings.Update)
|
||||
store.dispatch(SendAction.Warnings.Update)
|
||||
|
|
@ -131,6 +141,8 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
|
|||
sellService = makeSellExchangeService(config),
|
||||
primaryRules = CardExchangeRules(cardProvider),
|
||||
)
|
||||
// TODO: for refactoring (after remove old design refactor CurrencyExchangeManager and use 1 instance)
|
||||
store.state.daggerGraphState.get(DaggerGraphState::appStateHolder).exchangeService = exchangeManager
|
||||
store.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager))
|
||||
store.dispatchOnMain(GlobalAction.ExchangeManager.Update)
|
||||
}
|
||||
|
|
@ -143,28 +155,6 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
|
|||
}
|
||||
scope.launch { exchangeManager.update() }
|
||||
}
|
||||
is GlobalAction.ScanCard -> {
|
||||
scope.launch {
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(null)
|
||||
val result = tangemSdkManager.scanProduct(
|
||||
userTokensRepository = userTokensRepository,
|
||||
additionalBlockchainsToDerive = action.additionalBlockchainsToDerive,
|
||||
messageRes = action.messageResId,
|
||||
)
|
||||
withMainContext {
|
||||
store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data)
|
||||
action.onSuccess?.invoke(result.data)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
action.onFailure?.invoke(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is GlobalAction.FetchUserCountry -> {
|
||||
scope.launch {
|
||||
// TODO("After adding DI") get dependencies by DI
|
||||
|
|
@ -186,6 +176,28 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
|
|||
is GlobalAction.SetTopUpController -> {
|
||||
walletCurrenciesManager.addListener(action.topUpController)
|
||||
}
|
||||
is GlobalAction.UpdateUserWalletsListManager -> {
|
||||
/*
|
||||
* If UserWalletsListManager's implementation is changed,
|
||||
* then all selectedUserWallet's observers is became irrelevant
|
||||
*/
|
||||
action.manager.selectedUserWallet
|
||||
.distinctUntilChanged()
|
||||
.onEach { userWallet ->
|
||||
Analytics.send(event = Basic.WalletOpened())
|
||||
|
||||
store.state.globalState.feedbackManager?.infoHolder?.let { infoHolder ->
|
||||
infoHolder.setCardInfo(data = userWallet.scanResponse)
|
||||
|
||||
store.state.daggerGraphState.get(DaggerGraphState::walletManagersFacade)
|
||||
.getAll(userWalletId = userWallet.walletId)
|
||||
.onEach(infoHolder::setWalletsInfo)
|
||||
.launchIn(scope)
|
||||
}
|
||||
}
|
||||
.flowOn(Dispatchers.IO)
|
||||
.launchIn(scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,6 +97,9 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
|
|||
userWalletsListManager = action.manager,
|
||||
)
|
||||
}
|
||||
is GlobalAction.ChangeAppThemeMode -> globalState.copy(
|
||||
appThemeMode = action.appThemeMode,
|
||||
)
|
||||
else -> globalState
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.common.redux.global
|
||||
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.tap.common.analytics.topup.TopUpController
|
||||
|
|
@ -29,6 +30,7 @@ data class GlobalState(
|
|||
val userCountryCode: String? = null,
|
||||
val userWalletsListManager: UserWalletsListManager? = null,
|
||||
val topUpController: TopUpController? = null,
|
||||
val appThemeMode: AppThemeMode = AppThemeMode.DEFAULT,
|
||||
) : StateType
|
||||
|
||||
typealias CryptoCurrencyName = String
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.tap.common.redux.legacy
|
||||
|
||||
import com.tangem.domain.redux.LegacyAction
|
||||
import com.tangem.tap.common.feedback.RateCanBeBetterEmail
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
internal object LegacyMiddleware {
|
||||
val legacyMiddleware: Middleware<AppState> = { _, _ ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is LegacyAction.SendEmailRateCanBeBetter -> {
|
||||
store.state.globalState.feedbackManager?.sendEmail(RateCanBeBetterEmail())
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.common.ui
|
|||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -39,7 +40,7 @@ object SimpleCancelableAlertDialog {
|
|||
secondaryButtonAction: () -> Unit = {},
|
||||
context: Context,
|
||||
): AlertDialog {
|
||||
return AlertDialog.Builder(context).apply {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(titleRes?.let { context.getString(it) } ?: title)
|
||||
setMessage(messageRes?.let { context.getString(it) } ?: message)
|
||||
setPositiveButton(context.getText(primaryButtonRes)) { _, _ -> primaryButtonAction() }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue