Updated on 2026-08-14

This commit is contained in:
Tangem 2022-04-14 17:36:55 +03:00
commit 09932cf44e
148 changed files with 4011 additions and 414 deletions

View file

@ -68,6 +68,9 @@ repositories {
dependencies {
implementation fileTree(include: ['*.aar'], dir: 'libs')
implementation implementation(project(path: ':domain'))
// TODO: refactoring: only for backwards compatibility with non-relocated services to the network module
implementation implementation(project(path: ':network'))
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
@ -119,6 +122,7 @@ dependencies {
implementation 'com.github.salomonbrys.kotson:kotson:2.5.0'
// Network and Json
//TODO: refactoring: remove it when all network services moved to the network module
implementation 'com.squareup.retrofit2:retrofit:2.8.1'
implementation 'com.squareup.retrofit2:converter-moshi:2.6.0'
implementation 'com.squareup.moshi:moshi:1.12.0'

View file

@ -3,12 +3,12 @@ package com.tangem.tap
import android.content.Intent
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.snackbar.Snackbar
import com.tangem.TangemSdk
import com.tangem.domain.common.FeatureCoroutineExceptionHandler
import com.tangem.operations.backup.BackupService
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tap.common.DialogManager
@ -27,12 +27,9 @@ import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.shop.redux.ShopAction
import com.tangem.wallet.R
import com.tangem.wallet.databinding.ActivityMainBinding
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import java.io.PrintWriter
import java.io.StringWriter
import java.lang.ref.WeakReference
import kotlin.coroutines.CoroutineContext
@ -42,23 +39,13 @@ lateinit var backupService: BackupService
var notificationsHandler: NotificationsHandler? = null
private val coroutineContext: CoroutineContext
get() = Job() + Dispatchers.IO + initCoroutineExceptionHandler()
get() = Job() + Dispatchers.IO + FeatureCoroutineExceptionHandler.create("scope")
val scope = CoroutineScope(coroutineContext)
private val mainCoroutineContext: CoroutineContext
get() = Job() + Dispatchers.Main
get() = Job() + Dispatchers.Main + FeatureCoroutineExceptionHandler.create("mainScope")
val mainScope = CoroutineScope(mainCoroutineContext)
private fun initCoroutineExceptionHandler(): CoroutineExceptionHandler {
return CoroutineExceptionHandler { _, throwable ->
val sw = StringWriter()
throwable.printStackTrace(PrintWriter(sw))
val exceptionAsString: String = sw.toString()
Log.e("Coroutine", exceptionAsString)
throw throwable
}
}
class MainActivity : AppCompatActivity(), SnackbarHandler {
private var snackbar: Snackbar? = null

View file

@ -7,6 +7,7 @@ import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
import com.tangem.Log
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.network.common.MoshiConverter
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
import com.tangem.tap.common.images.PicassoHelper
import com.tangem.tap.common.redux.AppState
@ -24,7 +25,6 @@ import com.tangem.tap.features.feedback.AdditionalEmailInfo
import com.tangem.tap.features.feedback.FeedbackManager
import com.tangem.tap.features.feedback.TangemLogCollector
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.network.createMoshi
import com.tangem.tap.persistence.PreferencesStorage
import com.tangem.wallet.BuildConfig
import org.rekotlin.Store
@ -72,7 +72,7 @@ class TapApplication : Application() {
}
private fun loadConfigs() {
val moshi = createMoshi()
val moshi = MoshiConverter.defaultMoshi()
val localLoader = FeaturesLocalLoader(this, moshi)
val remoteLoader = FeaturesRemoteLoader(moshi)
val configManager = ConfigManager(localLoader, remoteLoader)

View file

@ -0,0 +1,7 @@
package com.tangem.tap
/**
[REDACTED_AUTHOR]
*/
val DELAY_SDK_DIALOG_CLOSE = 1400L

View file

@ -0,0 +1,31 @@
package com.tangem.tap.common.compose
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.form.Field
@Composable
fun BlockchainSpinner(
@StringRes title: Int,
itemList: List<Blockchain>,
selectedItem: Field.Data<Blockchain>,
isEnabled: Boolean = true,
textFieldConverter: (Blockchain) -> String,
dropdownItemView: @Composable ((Blockchain) -> Unit)? = null,
onItemSelected: (Blockchain) -> Unit,
) {
OutlinedSpinner(
modifier = Modifier.fillMaxWidth(),
label = stringResource(id = title),
itemList = itemList,
selectedItem = selectedItem,
textFieldConverter = textFieldConverter,
dropdownItemView = dropdownItemView,
isEnabled = isEnabled,
onItemSelected = onItemSelected
)
}

View file

@ -0,0 +1,115 @@
package com.tangem.tap.common.compose
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.tap.common.compose.extensions.stringResourceDefault
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
@Composable
fun RectangleButton(
modifier: Modifier = Modifier,
text: String = "",
textId: Int? = null,
isEnabled: Boolean = true,
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
leadingView: @Composable RowScope.() -> Unit = {},
middleView: @Composable RowScope.() -> Unit = { TextInButton(text = text, textId = textId) },
trailingView: @Composable RowScope.() -> Unit = {},
onClick: () -> Unit,
) {
Button(
modifier = modifier,
contentPadding = contentPadding,
enabled = isEnabled,
onClick = onClick,
) {
leadingView()
middleView()
trailingView()
}
}
@Composable
private fun TextInButton(
modifier: Modifier = Modifier,
text: String = "",
textId: Int? = null,
) {
Text(
modifier = modifier,
text = stringResourceDefault(textId, text),
maxLines = 1,
style = TextStyle(
fontSize = 16.sp,
lineHeight = 20.sp,
fontWeight = FontWeight.Medium,
)
)
}
@Composable
fun PasteButton(
modifier: Modifier = Modifier,
enabled: Boolean = true,
dpSize: DpSize = DpSize(40.dp, 40.dp),
onClick: () -> Unit,
content: @Composable (() -> Unit)? = null
) {
IconButton(
modifier = modifier.size(dpSize),
enabled = enabled,
onClick = onClick,
) {
when (content) {
null -> {
val icon = if (enabled) R.drawable.ic_paste else R.drawable.ic_paste_disabled
Icon(painterResource(id = icon), contentDescription = "Paste")
}
else -> content()
}
}
}
@Preview
@Composable
fun ButtonTest() {
Scaffold(
) {
Column(modifier = Modifier.padding(16.dp)) {
PreviewItem("Button") {
RectangleButton(text = "Some button") {}
}
PreviewItem("PasteButton") {
PasteButton(onClick = {})
}
}
}
}
@Composable
fun PreviewItem(
name: String,
content: @Composable RowScope.() -> Unit,
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
modifier = Modifier.weight(1f),
text = name,
)
content()
}
SpacerH8()
}

View file

@ -0,0 +1,24 @@
package com.tangem.tap.common.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.tangem.domain.common.util.ValueDebouncer
/**
[REDACTED_AUTHOR]
* This is an empty compose view. It just remember the ValueDebouncer inside of itself.
*/
@Composable
fun <T> valueDebouncerAsState(
debounce: Long = 400,
onValueChanged: (T) -> Unit
): ValueDebouncer<T> {
return remember {
ValueDebouncer(
debounce = debounce,
onValueChanged = { changedValue ->
changedValue?.let { onValueChanged(it) }
},
)
}
}

View file

@ -0,0 +1,144 @@
package com.tangem.tap.common.compose
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.tangem.domain.DomainDialog
import com.tangem.domain.DomainStateDialog
import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.global.DomainGlobalAction
import com.tangem.domain.redux.global.DomainGlobalState
import com.tangem.tap.features.tokens.addCustomToken.DomainErrorConverter
import com.tangem.tap.features.tokens.addCustomToken.compose.SelectTokenNetworkDialog
import com.tangem.wallet.R
import org.rekotlin.StoreSubscriber
@Composable
fun ComposeDialogManager() {
val dialogSate = remember { mutableStateOf<DomainStateDialog?>(null) }
val subscriber = remember {
object : StoreSubscriber<DomainGlobalState> {
override fun newState(state: DomainGlobalState) {
dialogSate.value = state.dialog
}
}
}
ShowTheDialog(dialogSate)
LaunchedEffect(key1 = Unit, block = {
domainStore.subscribe(subscriber) { state ->
state.skipRepeats { oldState, newState ->
oldState.globalState == newState.globalState
}.select { it.globalState }
}
})
DisposableEffect(key1 = Unit, effect = {
onDispose { domainStore.unsubscribe(subscriber) }
})
}
@Composable
private fun ShowTheDialog(dialogState: MutableState<DomainStateDialog?>) {
if (dialogState.value == null) return
val context = LocalContext.current
val errorConverter = remember { DomainErrorConverter(context) }
val onDismissRequest = { domainStore.dispatch(DomainGlobalAction.ShowDialog(null)) }
when (val dialog = dialogState.value) {
is DomainDialog.DialogError -> ErrorDialog(
title = stringResource(id = R.string.common_error),
body = errorConverter.convertError(dialog.error),
onDismissRequest
)
is DomainDialog.SelectTokenDialog -> SelectTokenNetworkDialog(dialog, onDismissRequest)
}
}
/**
* Dialog with single item selection
*/
@Composable
fun <T> SimpleDialog(
title: String,
items: List<T>,
onSelect: (T) -> Unit,
onDismissRequest: () -> Unit,
itemContent: @Composable (T) -> Unit,
) {
Dialog(
properties = DialogProperties(false, false),
onDismissRequest = { }
) {
Surface(
modifier = Modifier.fillMaxWidth(),
shape = MaterialTheme.shapes.medium
) {
Column(
modifier = Modifier.padding(22.dp)
) {
DialogTitle(title = title)
LazyColumn() {
items(items) { item ->
Row(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.clickable {
onSelect(item)
onDismissRequest()
},
verticalAlignment = Alignment.CenterVertically,
) { itemContent(item) }
}
}
}
}
}
}
@Composable
private fun DialogTitle(title: String) {
Text(
text = title,
style = LocalTextStyle.provides(
TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 20.sp
)
).value
)
SpacerH16()
}
@Composable
fun ErrorDialog(
title: String,
body: String,
onDismissRequest: () -> Unit,
) {
AlertDialog(
title = { DialogTitle(title) },
text = { Text(body) },
onDismissRequest = onDismissRequest,
confirmButton = {
Button(onClick = onDismissRequest) {
Text(text = stringResource(id = R.string.common_ok))
}
}
)
}

View file

@ -0,0 +1,41 @@
package com.tangem.tap.common.compose
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
/**
[REDACTED_AUTHOR]
*/
@Composable
fun ErrorView(
text: String,
modifier: Modifier = Modifier,
style: TextStyle = LocalTextStyle.current
) {
Text(
text,
color = MaterialTheme.colors.error,
modifier = modifier,
style = style
)
}
@Preview
@Composable
fun ErrorViewTest() {
Scaffold(
) {
Box(Modifier.padding(16.dp)) {
ErrorView(text = "Some error description")
}
}
}

View file

@ -6,7 +6,7 @@ import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalView
sealed class Keyboard {
data class Opened(val height: Int): Keyboard()
data class Opened(val height: Int) : Keyboard()
object Closed : Keyboard()
}
@ -14,12 +14,21 @@ sealed class Keyboard {
fun keyboardAsState(): State<Keyboard> {
val keyboardState: MutableState<Keyboard> = remember { mutableStateOf(Keyboard.Closed) }
val view = LocalView.current
val discrepancy = remember {
mutableStateOf(0)
}
DisposableEffect(view) {
val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener {
val rect = Rect()
view.getWindowVisibleDisplayFrame(rect)
val screenHeight = view.rootView.height
val keypadHeight = screenHeight - rect.bottom
val keypadHeight: Int = screenHeight - (rect.bottom + rect.top) - discrepancy.value
if (discrepancy.value == 0) {
discrepancy.value = keypadHeight;
if (keypadHeight == 0) discrepancy.value = 1
}
keyboardState.value = if (keypadHeight > screenHeight * 0.15) {
Keyboard.Opened(keypadHeight)
} else {

View file

@ -0,0 +1,90 @@
package com.tangem.tap.common.compose
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.VoidCallback
import com.tangem.domain.common.form.Field
import com.tangem.tap.common.extensions.ValueCallback
/**
[REDACTED_AUTHOR]
*/
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun <T> OutlinedSpinner(
modifier: Modifier = Modifier,
label: String,
itemList: List<T>,
selectedItem: Field.Data<T>,
onItemSelected: ValueCallback<T>,
textFieldConverter: (T) -> String = { it.toString() },
dropdownItemView: @Composable ((T) -> Unit)? = null,
isEnabled: Boolean = true,
onClose: VoidCallback = {}
) {
val rIsExpanded = remember { mutableStateOf(false) }
val rSelectedItem = remember { mutableStateOf(selectedItem.value) }
if (!selectedItem.isUserInput) {
rSelectedItem.value = selectedItem.value
}
val onItemSelectedInternal: (T) -> Unit = {
rSelectedItem.value = it
rIsExpanded.value = false
onItemSelected(it)
}
val onDismissRequest = {
rIsExpanded.value = false
onClose()
}
ExposedDropdownMenuBox(
expanded = rIsExpanded.value,
onExpandedChange = { rIsExpanded.value = !rIsExpanded.value },
) {
ProvideTextStyle(value = TextStyle(color = Color.Blue)) {
OutlinedTextField(
modifier = modifier,
readOnly = true,
enabled = isEnabled,
value = textFieldConverter(rSelectedItem.value),
onValueChange = {},
label = { Text(label) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = rIsExpanded.value) },
)
}
ExposedDropdownMenu(
expanded = rIsExpanded.value,
onDismissRequest = onDismissRequest,
) {
itemList.forEach { item ->
DropdownMenuItem(onClick = { onItemSelectedInternal(item) }) {
when (dropdownItemView) {
null -> Text(textFieldConverter(item))
else -> dropdownItemView(item)
}
}
}
}
}
}
@Preview
@Composable
fun TestSpinnerPreview() {
Scaffold() {
OutlinedSpinner(
label = "Blockchain name",
itemList = listOf(Blockchain.values()),
selectedItem = Field.Data(Blockchain.Avalanche),
onItemSelected = {},
)
}
}

View file

@ -0,0 +1,202 @@
package com.tangem.tap.common.compose
import androidx.compose.animation.*
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.Scaffold
import androidx.compose.material.Text
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.text.TextStyle
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.domain.DomainError
import com.tangem.domain.ErrorConverter
import com.tangem.domain.common.form.Field
import com.tangem.tap.common.compose.extensions.stringResourceDefault
/**
[REDACTED_AUTHOR]
*/
@Composable
fun OutlinedTextFieldWidget(
modifier: Modifier = Modifier,
textFieldData: 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: DomainError? = null,
errorConverter: ErrorConverter<String>? = null,
debounceTextChanges: Long = 400,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
onTextChanged: (String) -> Unit,
) {
if (!isVisible) return
Column(
modifier = modifier.animateContentSize(),
) {
OutlinedProgressTextField(
modifier = modifier,
textFieldData = textFieldData,
label = stringResourceDefault(labelId, label),
placeholder = stringResourceDefault(placeholderId, placeholder),
trailingIcon = trailingIcon,
isEnabled = isEnabled,
isLoading = isLoading,
error = error,
debounce = debounceTextChanges,
visualTransformation = visualTransformation,
keyboardOptions = keyboardOptions,
onTextChanged = onTextChanged
)
errorConverter?.let { AnimatedErrorView(error, it) }
}
}
@Composable
private fun OutlinedProgressTextField(
modifier: Modifier = Modifier,
textFieldData: Field.Data<String>,
label: String = "",
placeholder: String = "",
isEnabled: Boolean = true,
isLoading: Boolean = false,
error: DomainError? = null,
debounce: Long = 400,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
trailingIcon: @Composable (() -> Unit)? = null,
onTextChanged: (String) -> Unit,
) {
val rTextDebouncer = valueDebouncerAsState(debounce, onTextChanged)
val rText = remember { mutableStateOf(textFieldData.value) }
fun updateFieldValueAndEmmit(value: String) {
rText.value = value
rTextDebouncer.emmit(value)
}
// This action came from redux. Update the field value and send a new event as if from the user
if (!textFieldData.isUserInput) {
updateFieldValueAndEmmit(textFieldData.value)
}
Box {
OutlinedTextField(
modifier = Modifier
.fillMaxWidth(),
value = rText.value,
onValueChange = ::updateFieldValueAndEmmit,
keyboardOptions = keyboardOptions,
label = { Text(label) },
placeholder = { Text(placeholder) },
trailingIcon = trailingIcon,
singleLine = true,
enabled = isEnabled,
isError = error != null,
visualTransformation = visualTransformation,
)
AnimatedVisibility(
modifier = modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.padding(start = 6.dp, top = 0.dp, end = 6.dp, bottom = 6.dp),
visible = isLoading,
) { LinearProgressIndicator() }
}
}
@Composable
private fun AnimatedErrorView(
error: DomainError? = null,
errorConverter: ErrorConverter<String>,
) {
AnimatedVisibility(
visible = error != null,
enter = fadeIn() + slideInVertically(),
exit = slideOutVertically() + fadeOut(),
) {
error?.let {
ErrorView(errorConverter.convertError(it), style = TextStyle(fontSize = 14.sp))
}
}
}
@Preview
@Composable
fun OutlinedTextFieldWithErrorTest() {
val converter = remember {
object : ErrorConverter<String> {
override fun convertError(error: DomainError): String {
return "Hello, i'am the error: ${error::class.java.simpleName}"
}
}
}
class SimpleError(
override val code: Int = 1,
override val message: String = "Error message",
override val data: Any? = null,
) : DomainError
val modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
Scaffold(
) {
Column(
) {
OutlinedTextFieldWidget(
modifier = modifier,
textFieldData = Field.Data(""),
label = "First label",
placeholder = "1 placeholder",
error = null,
errorConverter = converter,
) {}
OutlinedTextFieldWidget(
modifier = modifier,
textFieldData = Field.Data("First"),
label = "First label",
placeholder = "1 placeholder",
error = null,
errorConverter = converter,
) {}
OutlinedTextFieldWidget(
modifier = modifier,
textFieldData = Field.Data("First"),
label = "First label",
placeholder = "1 placeholder",
isLoading = true,
error = null,
errorConverter = converter,
) {}
OutlinedTextFieldWidget(
modifier = modifier,
textFieldData = Field.Data("First"),
label = "First label",
placeholder = "1 placeholder",
error = SimpleError(),
errorConverter = converter,
) {}
}
}
}

View file

@ -0,0 +1,91 @@
package com.tangem.tap.common.compose
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
[REDACTED_AUTHOR]
*/
// ***************************** Horizontal
@Composable
fun SpacerH(height: Dp, modifier: Modifier = Modifier) {
Spacer(modifier = modifier.height(height))
}
@Composable
fun SpacerH4(modifier: Modifier = Modifier) {
SpacerH(4.dp, modifier)
}
@Composable
fun SpacerH8(modifier: Modifier = Modifier) {
SpacerH(8.dp, modifier)
}
@Composable
fun SpacerH16(modifier: Modifier = Modifier) {
SpacerH(16.dp, modifier)
}
@Composable
fun SpacerH24(modifier: Modifier = Modifier) {
SpacerH(24.dp, modifier)
}
// ***************************** Vertical
@Composable
fun SpacerW(width: Dp, modifier: Modifier = Modifier) {
Spacer(modifier = modifier.width(width))
}
@Composable
fun SpacerW4(modifier: Modifier = Modifier) {
SpacerW(4.dp, modifier)
}
@Composable
fun SpacerW8(modifier: Modifier = Modifier) {
SpacerW(8.dp, modifier)
}
@Composable
fun SpacerW16(modifier: Modifier = Modifier) {
SpacerW(16.dp, modifier)
}
@Composable
fun SpacerW24(modifier: Modifier = Modifier) {
SpacerW(24.dp, modifier)
}
// ***************************** Size
@Composable
fun SpacerS(size: Dp, modifier: Modifier = Modifier) {
Spacer(modifier = modifier.size(size))
}
@Composable
fun SpacerS4(modifier: Modifier = Modifier) {
SpacerS(4.dp, modifier)
}
@Composable
fun SpacerS8(modifier: Modifier = Modifier) {
SpacerS(8.dp, modifier)
}
@Composable
fun SpacerS16(modifier: Modifier = Modifier) {
SpacerS(16.dp, modifier)
}
@Composable
fun SpacerS24(modifier: Modifier = Modifier) {
SpacerS(24.dp, modifier)
}

View file

@ -0,0 +1,27 @@
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
)
}
}

View file

@ -1,4 +1,4 @@
package com.tangem.tap.common.extensions.compose
package com.tangem.tap.common.compose.extensions
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
@ -10,7 +10,11 @@ import androidx.core.graphics.red
/**
[REDACTED_AUTHOR]
*/
fun Color.argb(): Int {
fun Color.toAndroidGraphicsColor(): Int {
val argb = this.toArgb()
return android.graphics.Color.argb(argb.alpha, argb.red, argb.green, argb.blue)
}
fun Color.parse(hexColor: String): Color {
return Color(hexColor.removePrefix("#").toInt(16))
}

View file

@ -0,0 +1,19 @@
package com.tangem.tap.common.compose.extensions
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import com.tangem.tap.common.extensions.copyToClipboard
import com.tangem.tap.common.extensions.getFromClipboard
/**
[REDACTED_AUTHOR]
*/
@Composable
fun copyToClipboard(value: Any, label: String = "") {
LocalContext.current.copyToClipboard(value, label)
}
@Composable
fun getFromClipboard(default: CharSequence? = null): CharSequence? {
return LocalContext.current.getFromClipboard(default)
}

View file

@ -0,0 +1,21 @@
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
}
}

View file

@ -0,0 +1,6 @@
package com.tangem.tap.common.extensions
/**
[REDACTED_AUTHOR]
*/
typealias ValueCallback<T> = (T) -> Unit

View file

@ -20,6 +20,7 @@ import com.tangem.tap.features.onboarding.products.twins.ui.TwinsCardsFragment
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.shop.ui.ShopFragment
import com.tangem.tap.features.tokens.addCustomToken.AddCustomTokenFragment
import com.tangem.tap.features.tokens.ui.AddTokensFragment
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
import com.tangem.tap.features.wallet.ui.WalletFragment
@ -82,6 +83,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
AppScreen.DetailsSecurity -> DetailsSecurityFragment()
AppScreen.Disclaimer -> DisclaimerFragment()
AppScreen.AddTokens -> AddTokensFragment()
AppScreen.AddCustomToken -> AddCustomTokenFragment()
AppScreen.WalletDetails -> WalletDetailsFragment()
AppScreen.WalletConnectSessions -> WalletConnectSessionsFragment()
AppScreen.QrScan -> QrScanFragment()

View file

@ -9,9 +9,9 @@ import com.squareup.picasso.Transformation
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.IconsUtil
import com.tangem.blockchain.common.Token
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.tap.domain.extensions.getCustomIconUrl
import com.tangem.tap.domain.tokens.getIconUrl
import com.tangem.tap.domain.tokens.toNetworkId
import com.tangem.wallet.R
fun Picasso.loadCurrenciesIcon(

View file

@ -1,10 +1,11 @@
package com.tangem.tap.common.extensions
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.Dispatchers

View file

@ -1,5 +1,7 @@
package com.tangem.tap.common.redux
import com.tangem.domain.redux.DomainState
import com.tangem.domain.redux.domainStore
import com.tangem.tap.common.redux.global.GlobalMiddleware
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.common.redux.navigation.NavigationState
@ -49,6 +51,9 @@ data class AppState(
val shopState: ShopState = ShopState(),
) : StateType {
val domainState: DomainState
get() = domainStore.state
companion object {
fun getMiddleware(): List<Middleware<AppState>> {
return listOf(

View file

@ -4,13 +4,13 @@ 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.domain.common.ScanResponse
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
import com.tangem.tap.common.redux.*
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.features.feedback.EmailData
import com.tangem.tap.features.feedback.FeedbackManager

View file

@ -3,10 +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.ifNotNull
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.global.DomainGlobalAction
import com.tangem.tap.*
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.withMainContext
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
@ -100,10 +102,14 @@ private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState
store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
when (result) {
is CompletionResult.Success -> {
domainStore.dispatch(DomainGlobalAction.SetScanResponse(result.data))
tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data)
action.onSuccess?.invoke(result.data)
}
is CompletionResult.Failure -> action.onFailure?.invoke(result.error)
is CompletionResult.Failure -> {
domainStore.dispatch(DomainGlobalAction.SetScanResponse(null))
action.onFailure?.invoke(result.error)
}
}
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.tap.common.redux.global
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
import com.tangem.tap.common.redux.StateDialog
@ -7,7 +8,6 @@ import com.tangem.tap.domain.PayIdManager
import com.tangem.tap.domain.TapWalletManager
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.feedback.FeedbackManager
import com.tangem.tap.features.onboarding.OnboardingManager
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService

View file

@ -17,7 +17,7 @@ enum class AppScreen {
Wallet, WalletDetails,
Send,
Details, DetailsConfirm, DetailsSecurity,
AddTokens,
AddTokens, AddCustomToken,
WalletConnectSessions,
QrScan
}

View file

@ -1,9 +0,0 @@
package com.tangem.tap.domain
/**
[REDACTED_AUTHOR]
*/
enum class ProductType {
Note, Twins, Wallet
}

View file

@ -17,6 +17,7 @@ import com.tangem.common.core.Config
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.domain.common.ScanResponse
import com.tangem.operations.CommandResponse
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
@ -30,7 +31,6 @@ import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
import com.tangem.tap.domain.tasks.product.ScanProductTask
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.tokens.CurrenciesRepository
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.wallet.R

View file

@ -3,21 +3,23 @@ package com.tangem.tap.domain
import com.tangem.blockchain.blockchains.solana.RentProvider
import com.tangem.blockchain.common.*
import com.tangem.common.services.Result
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.common.ThrottlerWithValues
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.extensions.withMainContext
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.domain.extensions.makeWalletManagersForApp
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.tokens.CardCurrencies
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.wallet.models.PendingTransactionType
@ -34,7 +36,7 @@ import java.math.BigDecimal
class TapWalletManager {
val walletManagerFactory: WalletManagerFactory
by lazy { WalletManagerFactory(blockchainSdkConfig) }
by lazy { WalletManagerFactory(blockchainSdkConfig) }
private val coinMarketCapService = CoinMarketCapService()
private val blockchainSdkConfig by lazy {

View file

@ -2,14 +2,33 @@ package com.tangem.tap.domain.extensions
import com.tangem.common.card.Card
import com.tangem.common.card.CardWallet
import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
import com.tangem.domain.common.TapWorkarounds
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.TwinCardNumber
import com.tangem.domain.common.getTwinCardNumber
import com.tangem.domain.common.isTangemTwin
import com.tangem.operations.attestation.CardVerifyAndGetInfo
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.tap.domain.twins.getTwinCardNumber
import com.tangem.tap.features.wallet.redux.Artwork
val Card.remainingSignatures: Int?
get() = this.getSingleWallet()?.remainingSignatures
val Card.isWalletDataSupported: Boolean
get() = this.firmwareVersion.major >= 4
val Card.isMultiwalletAllowed: Boolean
get() {
return !isTangemTwin() && !isStart2Coin && !TapWorkarounds.isTangemNote(this)
&& (firmwareVersion >= FirmwareVersion.MultiWalletAvailable ||
getSingleWallet()?.curve == EllipticCurve.Secp256k1)
}
fun Card.getSingleWallet(): CardWallet? {
return wallets.firstOrNull()
}
@ -66,10 +85,4 @@ fun Card.getArtworkUrl(artworkId: String?): String? {
cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL
else -> null
}
}
val Card.remainingSignatures: Int?
get() = this.getSingleWallet()?.remainingSignatures
val Card.isWalletDataSupported: Boolean
get() = this.firmwareVersion.major >= 4
}

View file

@ -7,14 +7,16 @@ import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toMapKey
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.TapWorkarounds.useOldStyleDerivation
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.features.wallet.redux.Currency
fun WalletManagerFactory.makeWalletManagerForApp(
scanResponse: ScanResponse, blockchain: Blockchain, derivationParams: DerivationParams?
scanResponse: ScanResponse,
blockchain: Blockchain,
derivationParams: DerivationParams?
): WalletManager? {
val card = scanResponse.card
if (card.isTestCard && blockchain.getTestnetVersion() == null) return null
@ -31,7 +33,7 @@ fun WalletManagerFactory.makeWalletManagerForApp(
scanResponse.isTangemTwins() && scanResponse.secondTwinPublicKey != null -> {
makeTwinWalletManager(
card.cardId,
wallet.publicKey, scanResponse.secondTwinPublicKey.hexToBytes(),
wallet.publicKey, scanResponse.secondTwinPublicKey!!.hexToBytes(),
environmentBlockchain, wallet.curve
)
}

View file

@ -9,6 +9,11 @@ import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toMapKey
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.domain.common.KeyWalletPublicKey
import com.tangem.domain.common.ProductType
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.TapWorkarounds.getTangemNoteBlockchain
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.operations.CommandResponse
import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.backup.StartPrimaryCardLinkingTask
@ -16,12 +21,7 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.operations.wallet.CreateWalletTask
import com.tangem.tap.domain.ProductType
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.tasks.product.CreateWalletsTask
import com.tangem.tap.domain.tasks.product.KeyWalletPublicKey
import com.tangem.tap.domain.tasks.product.ProductCommandProcessor
import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets
import com.tangem.tap.features.demo.DemoHelper
@ -79,7 +79,7 @@ private class CreateWalletTangemNote : ProductCommandProcessor<CreateWalletRespo
return
}
val curvesSupportedByBlockchain = getTangemNoteBlockchain(card)?.getSupportedCurves()
val curvesSupportedByBlockchain = card.getTangemNoteBlockchain()?.getSupportedCurves()
if (curvesSupportedByBlockchain == null || curvesSupportedByBlockchain.isEmpty()) {
callback(CompletionResult.Failure(TangemSdkError.CardError()))
return

View file

@ -1,12 +1,10 @@
package com.tangem.tap.domain.tasks.product
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.common.CompletionResult
import com.tangem.common.card.Card
import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.card.WalletData
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.TangemError
@ -16,69 +14,25 @@ import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toHexString
import com.tangem.common.extensions.toMapKey
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.operations.CommandResponse
import com.tangem.domain.common.*
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.TapWorkarounds.isExcluded
import com.tangem.domain.common.TapWorkarounds.isNotSupportedInThatRelease
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.operations.PreflightReadMode
import com.tangem.operations.PreflightReadTask
import com.tangem.operations.ScanTask
import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.backup.StartPrimaryCardLinkingTask
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
import com.tangem.tap.domain.ProductType
import com.tangem.tap.domain.TapSdkError
import com.tangem.tap.domain.TapWorkarounds
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
import com.tangem.tap.domain.TapWorkarounds.isExcluded
import com.tangem.tap.domain.TapWorkarounds.isNotSupportedInThatRelease
import com.tangem.tap.domain.TapWorkarounds.useOldStyleDerivation
import com.tangem.tap.domain.extensions.getPrimaryCurve
import com.tangem.tap.domain.extensions.getSingleWallet
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.domain.tokens.CurrenciesRepository
import com.tangem.tap.domain.twins.TwinsHelper
import com.tangem.tap.preferencesStorage
data class ScanResponse(
val card: Card,
val productType: ProductType,
val walletData: WalletData?,
val secondTwinPublicKey: String? = null,
val derivedKeys: Map<KeyWalletPublicKey, ExtendedPublicKeysMap> = mapOf(),
val primaryCard: PrimaryCard? = null
) : CommandResponse {
fun getBlockchain(): Blockchain {
if (productType == ProductType.Note) return getTangemNoteBlockchain(card)
?: return Blockchain.Unknown
val blockchainName: String = walletData?.blockchain ?: return Blockchain.Unknown
return Blockchain.fromId(blockchainName)
}
fun getPrimaryToken(): Token? {
val cardToken = walletData?.token ?: return null
return Token(
cardToken.name,
cardToken.symbol,
cardToken.contractAddress,
cardToken.decimals,
)
}
fun isTangemNote(): Boolean = productType == ProductType.Note
fun isTangemWallet(): Boolean = productType == ProductType.Wallet
fun isTangemTwins(): Boolean = productType == ProductType.Twins
fun supportsHdWallet(): Boolean = card.settings.isHDWalletAllowed
fun supportsBackup(): Boolean = card.settings.isBackupAllowed
fun twinsIsTwinned(): Boolean =
card.isTangemTwins() && walletData != null && secondTwinPublicKey != null
}
typealias KeyWalletPublicKey = ByteArrayKey
class ScanProductTask(
val card: Card? = null,
private val currenciesRepository: CurrenciesRepository?,
@ -134,8 +88,6 @@ class ScanProductTask(
}
}
private fun Card.isTangemTwins(): Boolean = TwinsHelper.getTwinCardNumber(cardId) != null
private class ScanNoteProcessor : ProductCommandProcessor<ScanResponse> {
override fun proceed(
card: Card,
@ -299,12 +251,12 @@ private class ScanWalletProcessor(
if (!card.useOldStyleDerivation) {
blockchainsToDerive.removeAll(
listOf(
Blockchain.BSC, Blockchain.BSCTestnet,
Blockchain.Polygon, Blockchain.PolygonTestnet,
Blockchain.RSK,
Blockchain.Fantom, Blockchain.FantomTestnet,
Blockchain.Avalanche, Blockchain.AvalancheTestnet,
).map { BlockchainNetwork(it, card) }
Blockchain.BSC, Blockchain.BSCTestnet,
Blockchain.Polygon, Blockchain.PolygonTestnet,
Blockchain.RSK,
Blockchain.Fantom, Blockchain.FantomTestnet,
Blockchain.Avalanche, Blockchain.AvalancheTestnet,
).map { BlockchainNetwork(it, card) }
)
}
return blockchainsToDerive.distinct()

View file

@ -12,18 +12,18 @@ import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.card.Card
import com.tangem.common.card.FirmwareVersion
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.network.common.MoshiConverter
import com.tangem.tap.common.extensions.appendIf
import com.tangem.tap.common.extensions.readJsonFileToString
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
import com.tangem.tap.domain.extensions.setCustomIconUrl
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.network.createMoshi
import timber.log.Timber
import java.util.*
class CurrenciesRepository(val context: Application) {
private val moshi = createMoshi()
private val moshi = MoshiConverter.defaultMoshi()
private val blockchainsAdapter: JsonAdapter<List<Blockchain>> = moshi.adapter(
Types.newParameterizedType(List::class.java, Blockchain::class.java)
)

View file

@ -2,6 +2,7 @@ package com.tangem.tap.domain.tokens
import com.squareup.moshi.JsonClass
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.extensions.fromNetworkId
@JsonClass(generateAdapter = true)
data class CurrencyFromJson(
@ -74,62 +75,4 @@ data class Contract(
fun getIconUrl(id: String): String {
return "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/$id.png"
}
fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
return when (networkId) {
"avalanche" -> Blockchain.Avalanche
"binancecoin" -> Blockchain.Binance
"binance-smart-chain" -> Blockchain.BSC
"ethereum" -> Blockchain.Ethereum
"polygon-pos" -> Blockchain.Polygon
"solana" -> Blockchain.Solana
"fantom" -> Blockchain.Fantom
"bitcoin" -> Blockchain.Bitcoin
"bitcoin-cash" -> Blockchain.BitcoinCash
"cardano" -> Blockchain.CardanoShelley
"dogecoin" -> Blockchain.Dogecoin
"ducatus" -> Blockchain.Ducatus
"litecoin" -> Blockchain.Litecoin
"rsk" -> Blockchain.RSK
"stellar" -> Blockchain.Stellar
"tezos" -> Blockchain.Tezos
"ripple" -> Blockchain.XRP
else -> null
}
}
fun Blockchain.toNetworkId(): String {
return when (this) {
Blockchain.Unknown -> "unknown"
Blockchain.Avalanche -> "avalanche"
Blockchain.AvalancheTestnet -> "avalaunche"
Blockchain.Binance -> "binancecoin"
Blockchain.BinanceTestnet -> "binancecoin"
Blockchain.BSC -> "binance-smart-chain"
Blockchain.BSCTestnet -> "binance-smart-chain"
Blockchain.Bitcoin -> "bitcoin"
Blockchain.BitcoinTestnet -> "bitcoin"
Blockchain.BitcoinCash -> "bitcoin-cash"
Blockchain.BitcoinCashTestnet -> "bitcoin-cash"
Blockchain.Cardano -> "cardano"
Blockchain.CardanoShelley -> "cardano"
Blockchain.Dogecoin -> "dogecoin"
Blockchain.Ducatus -> "ducatus"
Blockchain.Ethereum -> "ethereum"
Blockchain.EthereumTestnet -> "ethereum"
Blockchain.Fantom -> "fantom"
Blockchain.FantomTestnet -> "fantom"
Blockchain.Litecoin -> "litecoin"
Blockchain.Polygon -> "matic-network"
Blockchain.PolygonTestnet -> "matic-networks"
Blockchain.RSK -> "rootstock"
Blockchain.Stellar -> "stellar"
Blockchain.StellarTestnet -> "stellar"
Blockchain.Solana -> "solana"
Blockchain.SolanaTestnet -> "solana"
Blockchain.Tezos -> "tezos"
Blockchain.XRP -> "ripple"
}
}

View file

@ -8,6 +8,7 @@ import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.TangemError
import com.tangem.common.extensions.hexToBytes
import com.tangem.domain.common.TwinsHelper
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.operations.wallet.CreateWalletTask
import com.tangem.operations.wallet.PurgeWalletCommand

View file

@ -4,10 +4,10 @@ import com.tangem.common.CompletionResult
import com.tangem.common.KeyPair
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.domain.common.ScanResponse
import com.tangem.operations.PreflightReadMode
import com.tangem.operations.PreflightReadTask
import com.tangem.tap.domain.tasks.product.ScanProductTask
import com.tangem.tap.domain.tasks.product.ScanResponse
class FinalizeTwinTask(
private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair,

View file

@ -10,11 +10,11 @@ import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.domain.common.ScanResponse
import com.tangem.network.common.MoshiConverter
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.network.createMoshi
import com.tangem.tap.tangemSdkManager
class TwinCardsManager(
@ -107,7 +107,7 @@ class TwinCardsManager(
}
private fun getAdapter(): JsonAdapter<List<Issuer>> {
return createMoshi().adapter(
return MoshiConverter.defaultMoshi().adapter(
Types.newParameterizedType(List::class.java, Issuer::class.java)
)
}

View file

@ -6,6 +6,7 @@ import android.animation.PropertyValuesHolder
import android.view.View
import androidx.core.animation.doOnEnd
import com.tangem.common.extensions.VoidCallback
import com.tangem.domain.common.TwinCardNumber
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapView
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapViewState
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget

View file

@ -5,14 +5,14 @@ import android.content.Context
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Types
import com.tangem.network.common.MoshiConverter
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
import com.tangem.tap.features.details.redux.walletconnect.WalletForSession
import com.tangem.tap.network.createMoshi
import com.trustwallet.walletconnect.models.WCPeerMeta
import com.trustwallet.walletconnect.models.session.WCSession
class WalletConnectRepository(val context: Application) {
private val moshi = createMoshi()
private val moshi = MoshiConverter.defaultMoshi()
private val walletConnectAdapter: JsonAdapter<List<SessionDao>> = moshi.adapter(
Types.newParameterizedType(List::class.java, SessionDao::class.java)
)

View file

@ -5,9 +5,9 @@ import com.tangem.blockchain.common.*
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.CompletionResult
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.extensions.dispatchNotification
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.features.wallet.redux.WalletAction

View file

@ -1,9 +1,9 @@
package com.tangem.tap.features.demo
import com.tangem.common.extensions.guard
import com.tangem.tap.common.extensions.withMainContext
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.ProgressState

View file

@ -1,7 +1,7 @@
package com.tangem.tap.features.demo
import com.tangem.blockchain.common.WalletManager
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.domain.common.ScanResponse
/**
[REDACTED_AUTHOR]

View file

@ -2,12 +2,12 @@ package com.tangem.tap.features.details.redux
import com.tangem.blockchain.common.Wallet
import com.tangem.common.card.Card
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TwinCardNumber
import com.tangem.operations.pins.CheckUserCodesResponse
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.termsOfUse.CardTou
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.tap.network.coinmarketcap.FiatCurrency
import com.tangem.wallet.R
import org.rekotlin.Action

View file

@ -2,12 +2,12 @@ package com.tangem.tap.features.details.redux
import com.tangem.common.card.Card
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.isTangemTwin
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
import com.tangem.tap.domain.extensions.isWalletDataSupported
import com.tangem.tap.domain.extensions.signedHashesCount
import com.tangem.tap.domain.extensions.toSendableAmounts
import com.tangem.tap.domain.twins.isTangemTwin
import com.tangem.tap.features.wallet.models.hasPendingTransactions
import org.rekotlin.Action
import java.util.*
@ -64,8 +64,8 @@ private fun handleEraseWallet(
val card = state.scanResponse?.card
val notAllowedByAnyWallet = card?.wallets?.any { it.settings.isPermanent } ?: false
val notAllowedByCard = notAllowedByAnyWallet ||
(card?.isWalletDataSupported == true &&
(!state.scanResponse.isTangemNote() && !state.scanResponse.supportsBackup()))
(card?.isWalletDataSupported == true &&
(!state.scanResponse.isTangemNote() && !state.scanResponse.supportsBackup()))
val notEmpty = state.wallets.any {
it.hasPendingTransactions() || it.amounts.toSendableAmounts().isNotEmpty()
@ -136,7 +136,7 @@ private fun handleSecurityAction(
is DetailsAction.ManageSecurity.OpenSecurity -> {
val allowedSecurityOptions = when {
state.scanResponse?.card?.isStart2Coin == true ||
state.scanResponse?.isTangemNote() == true -> {
state.scanResponse?.isTangemNote() == true -> {
EnumSet.of(SecurityOption.LongTap)
}
state.scanResponse?.supportsBackup() == true -> {

View file

@ -2,10 +2,10 @@ package com.tangem.tap.features.details.redux
import android.net.Uri
import com.tangem.blockchain.common.Wallet
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.entities.Button
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
import com.tangem.tap.network.coinmarketcap.FiatCurrency
import com.tangem.tap.store

View file

@ -1,8 +1,8 @@
package com.tangem.tap.features.details.redux.walletconnect
import android.app.Activity
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.wallet.R
import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder
import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder

View file

@ -4,6 +4,9 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationParams
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.guard
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.getFromClipboard
import com.tangem.tap.common.redux.AppState
@ -11,11 +14,8 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.isMultiwalletAllowed
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.domain.walletconnect.BnbHelper
import com.tangem.tap.domain.walletconnect.WalletConnectManager

View file

@ -5,8 +5,8 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
import com.trustwallet.walletconnect.models.WCPeerMeta

View file

@ -7,12 +7,12 @@ import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.domain.common.getTwinCardIdForUser
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.isMultiwalletAllowed
import com.tangem.tap.domain.twins.getTwinCardIdForUser
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.details.redux.SecurityOption

View file

@ -7,10 +7,10 @@ import android.os.Build
import com.tangem.Log
import com.tangem.TangemSdkLogger
import com.tangem.blockchain.common.*
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds
import com.tangem.tap.common.extensions.sendEmail
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.domain.TapWorkarounds
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.wallet.R
import timber.log.Timber
import java.io.File

View file

@ -1,6 +1,8 @@
package com.tangem.tap.features.home.compose
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Text
@ -12,6 +14,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.tap.common.compose.SpacerS8
import com.tangem.wallet.R
@Composable
@ -45,7 +48,7 @@ fun HomeButtons(
maxLines = 1
)
}
Spacer(modifier = Modifier.size(8.dp))
SpacerS8()
Button(
modifier = Modifier
.weight(1f)

View file

@ -24,6 +24,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.tap.common.compose.SpacerS24
import androidx.compose.ui.unit.sp
import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.features.wallet.redux.ProgressState
@ -117,7 +118,7 @@ fun StoriesScreen(
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
) {
Spacer(modifier = Modifier.size(24.dp))
SpacerS24()
StoriesProgressBar(
steps = steps,
currentStep = currentStep.value,

View file

@ -22,7 +22,9 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import com.tangem.tangem_sdk_new.extensions.dpToPx
import com.tangem.tap.common.extensions.compose.argb
import com.tangem.tap.common.compose.SpacerS16
import com.tangem.tap.common.compose.SpacerS24
import com.tangem.tap.common.compose.extensions.toAndroidGraphicsColor
import com.tangem.wallet.R
@Composable
@ -53,11 +55,11 @@ fun StoriesGeneralContent(
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.size(16.dp))
SpacerS16()
SubtitleText(subtitleText, subtitleTextId)
Spacer(modifier = Modifier.size(25.dp))
SpacerS24()
if (imageSource != null) {
Image(
@ -93,7 +95,7 @@ fun SubtitleText(subtitleText: String, subtitleTextId: Int?) {
textAlignment = View.TEXT_ALIGNMENT_CENTER
typeface = Typeface.DEFAULT
setTextSize(TypedValue.COMPLEX_UNIT_SP, 20f)
setTextColor(color.argb())
setTextColor(color.toAndroidGraphicsColor())
}
}
}

View file

@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.tap.common.compose.SpacerW4
@Composable
fun StoriesProgressBar(
@ -67,7 +68,7 @@ fun StoriesProgressBar(
) {}
}
if (index != steps) {
Spacer(modifier = Modifier.width(4.dp))
SpacerW4()
}
}
}

View file

@ -1,12 +1,13 @@
package com.tangem.tap.features.home.redux
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.analytics.GetCardSourceParams
import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.onCardScanned
import com.tangem.tap.common.extensions.withMainContext
import com.tangem.tap.common.post
import com.tangem.tap.common.postUi
import com.tangem.tap.common.redux.AppState
@ -14,7 +15,6 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.features.home.redux.HomeMiddleware.Companion.BUY_WALLET_URL
import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
@ -50,7 +50,10 @@ private val homeMiddleware: Middleware<AppState> = { dispatch, state ->
postUi(700) { store.dispatch(HomeAction.ReadCard) }
}
}
is HomeAction.ReadCard -> handleReadCard()
is HomeAction.ReadCard -> {
handleReadCard()
// store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomTokens))
}
is HomeAction.GoToShop -> {
when (action.regionProvider.getRegion()?.toLowerCase()) {
"ru" -> store.dispatchOpenUrl(BUY_WALLET_URL)

View file

@ -1,9 +1,9 @@
package com.tangem.tap.features.onboarding
import com.tangem.domain.common.ProductType
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.domain.ProductType
import com.tangem.tap.domain.extensions.hasWallets
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.preferencesStorage
/**

View file

@ -5,13 +5,13 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.isZero
import com.tangem.common.services.Result
import com.tangem.domain.common.ScanResponse
import com.tangem.operations.attestation.CardVerifyAndGetInfo
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.common.extensions.isPositive
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.wallet.models.hasPendingTransactions
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.ProgressState

View file

@ -2,6 +2,8 @@ package com.tangem.tap.features.onboarding.products.note.redux
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.guard
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.postUi
import com.tangem.tap.common.redux.AppDialog
@ -9,7 +11,6 @@ import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.hasWallets
import com.tangem.tap.domain.extensions.makePrimaryWalletManager

View file

@ -1,14 +1,14 @@
package com.tangem.tap.features.onboarding.products.otherCards.redux
import com.tangem.common.CompletionResult
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.common.extensions.onCardScanned
import com.tangem.tap.common.extensions.withMainContext
import com.tangem.tap.common.postUi
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.domain.extensions.hasWallets
import com.tangem.tap.scope
import com.tangem.tap.store

View file

@ -3,9 +3,9 @@ package com.tangem.tap.features.onboarding.products.twins.redux
import com.tangem.Message
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.VoidCallback
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.twins.AssetReader
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.features.onboarding.OnboardingWalletBalance

View file

@ -3,6 +3,9 @@ package com.tangem.tap.features.onboarding.products.twins.redux
import com.tangem.blockchain.extensions.Result
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.guard
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.postUi
import com.tangem.tap.common.redux.AppDialog
@ -10,10 +13,8 @@ import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.ProgressState

View file

@ -1,7 +1,7 @@
package com.tangem.tap.features.onboarding.products.twins.redux
import com.tangem.domain.common.getTwinCardNumber
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.twins.getTwinCardNumber
import org.rekotlin.Action
class TwinCardsReducer {

View file

@ -1,9 +1,9 @@
package com.tangem.tap.features.onboarding.products.twins.redux
import com.tangem.blockchain.common.WalletManager
import com.tangem.domain.common.TwinCardNumber
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.buyIsAllowed
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
import com.tangem.tap.store

View file

@ -13,6 +13,7 @@ import com.squareup.picasso.Picasso
import com.tangem.Message
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.VoidCallback
import com.tangem.domain.common.TwinCardNumber
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.navigation.AppScreen
@ -21,7 +22,6 @@ import com.tangem.tap.common.redux.navigation.ShareElement
import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
import com.tangem.tap.domain.twins.AssetReader
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.tap.domain.twins.TwinsCardWidget
import com.tangem.tap.features.addBackPressHandler
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment

View file

@ -2,16 +2,16 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
import com.tangem.common.CompletionResult
import com.tangem.common.card.Card
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.operations.backup.BackupService
import com.tangem.tap.*
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.withMainContext
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.extensions.hasWallets
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.wallet.redux.Artwork

View file

@ -9,6 +9,9 @@ import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
import com.tangem.common.services.Result
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsParam
@ -17,10 +20,8 @@ import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.domain.TangemSigner
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.extensions.minimalAmount
import com.tangem.tap.domain.tokens.BlockchainNetwork

View file

@ -0,0 +1,62 @@
package com.tangem.tap.features.tokens.addCustomToken
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import androidx.appcompat.widget.Toolbar
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import com.google.accompanist.appcompattheme.AppCompatTheme
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
import com.tangem.domain.redux.domainStore
import com.tangem.tap.features.BaseStoreFragment
import com.tangem.tap.features.tokens.addCustomToken.compose.AddCustomTokenScreen
import com.tangem.wallet.R
import org.rekotlin.StoreSubscriber
/**
[REDACTED_AUTHOR]
*/
class AddCustomTokenFragment : BaseStoreFragment(R.layout.view_compose_fragment), StoreSubscriber<AddCustomTokenState> {
private var state: MutableState<AddCustomTokenState> = mutableStateOf(domainStore.state.addCustomTokensState)
override fun subscribeToStore() {
domainStore.subscribe(this) { state ->
state.skipRepeats { oldState, newState ->
oldState.addCustomTokensState == newState.addCustomTokensState
}.select { it.addCustomTokensState }
}
}
override fun newState(state: AddCustomTokenState) {
if (activity == null || view == null) return
this.state.value = state
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
requireActivity().window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
view.findViewById<Toolbar>(R.id.toolbar)?.let {
it.setTitle(R.string.add_custom_token_title)
}
view.findViewById<ComposeView>(R.id.view_compose)?.setContent {
AppCompatTheme(requireContext()) {
Box(modifier = Modifier
.fillMaxSize()
) {
AddCustomTokenScreen(state)
}
}
}
// addBackPressHandler(this)
}
}

View file

@ -0,0 +1,49 @@
package com.tangem.tap.features.tokens.addCustomToken
import android.content.Context
import com.tangem.domain.DomainError
import com.tangem.domain.ErrorConverter
import com.tangem.domain.features.addCustomToken.AddCustomTokenError
import com.tangem.domain.features.addCustomToken.AddCustomTokenWarning
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
class DomainErrorConverter(
private val context: Context
) : ErrorConverter<String> {
override fun convertError(error: DomainError): String {
val errorMessage = when (error) {
is AddCustomTokenError -> AddCustomTokenConverter(context).convertError(error)
else -> null
}
return errorMessage?.let { it } ?: "Unknown error: ${error::class.java.simpleName}"
}
}
private class AddCustomTokenConverter(
private val context: Context
) : ErrorConverter<String> {
override fun convertError(error: DomainError): String {
val customTokenError = (error as? AddCustomTokenError) ?: throw UnsupportedOperationException()
val rawMessage = when (customTokenError) {
AddCustomTokenWarning.PotentialScamToken -> R.string.custom_token_validation_error_not_found
AddCustomTokenWarning.TokenAlreadyAdded -> R.string.custom_token_validation_error_already_added
AddCustomTokenError.InvalidContractAddress -> R.string.custom_token_creation_error_invalid_contract_address
AddCustomTokenError.NetworkIsNotSelected -> R.string.custom_token_creation_error_network_not_selected
AddCustomTokenError.InvalidDerivationPath -> R.string.custom_token_creation_error_invalid_derivation_path
AddCustomTokenError.InvalidDecimalsCount -> R.string.custom_token_creation_error_wrong_decimals
AddCustomTokenError.FieldIsEmpty -> R.string.custom_token_creation_error_empty_fields
else -> null
}
return when (rawMessage) {
is Int -> context.getString(rawMessage)
// is String -> rawMessage
else -> "Unknown error: ${customTokenError::class.java.simpleName}"
}
}
}

View file

@ -0,0 +1,205 @@
package com.tangem.tap.features.tokens.addCustomToken.compose
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.domain.ErrorConverter
import com.tangem.domain.common.form.DataField
import com.tangem.domain.common.form.FieldId
import com.tangem.domain.features.addCustomToken.AddCustomTokenError
import com.tangem.domain.features.addCustomToken.AddCustomTokenWarning
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.*
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
import com.tangem.domain.features.addCustomToken.redux.ScreenState
import com.tangem.domain.features.addCustomToken.redux.ViewStates
import com.tangem.domain.redux.domainStore
import com.tangem.tap.common.compose.ComposeDialogManager
import com.tangem.tap.common.compose.keyboardAsState
import com.tangem.tap.features.tokens.addCustomToken.DomainErrorConverter
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
private class AddCustomTokenScreen {} // for simple search
@Composable
fun AddCustomTokenScreen(state: MutableState<AddCustomTokenState>) {
val scaffoldState = rememberScaffoldState()
Scaffold(
scaffoldState = scaffoldState,
backgroundColor = colorResource(id = R.color.backgroundLightGray),
floatingActionButton = {
HangingOverKeyboardView(keyboardState = keyboardAsState()) {
AddButton(state)
}
},
floatingActionButtonPosition = FabPosition.Center,
) {
Box(Modifier.fillMaxSize()) {
LazyColumn(
contentPadding = PaddingValues(bottom = 90.dp)
) {
// item { AddCustomTokenDebugActions() }
item {
Surface(
modifier = Modifier.padding(16.dp),
shape = MaterialTheme.shapes.small,
elevation = 4.dp,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
FormFields(state)
}
}
}
item { Warnings(state.value.warnings.toList()) }
}
}
ComposeDialogManager()
}
LaunchedEffect(key1 = Unit, block = { domainStore.dispatch(AddCustomTokenAction.OnCreate) })
DisposableEffect(key1 = Unit, effect = { onDispose { domainStore.dispatch(AddCustomTokenAction.OnDestroy) } })
}
@Composable
private fun FormFields(state: MutableState<AddCustomTokenState>) {
val context = LocalContext.current
val errorConverter = remember { DomainErrorConverter(context) }
val stateValue = state.value
stateValue.form.fieldList.forEach { field ->
val data = ScreenFieldData.fromState(field, stateValue, errorConverter)
when (field.id) {
ContractAddress -> TokenContractAddressView(data)
Network -> TokenNetworkView(data, stateValue)
Name -> TokenNameView(data)
Symbol -> TokenSymbolView(data)
Decimals -> TokenDecimalsView(data)
DerivationPath -> TokenDerivationPathView(data, stateValue)
}
}
}
@Composable
fun Warnings(warnings: List<AddCustomTokenWarning>) {
if (warnings.isEmpty()) return
val context = LocalContext.current
val warningConverter = remember { DomainErrorConverter(context) }
Column {
warnings.forEachIndexed { index, item ->
val modifier = when (index) {
0 -> Modifier.padding(16.dp, 16.dp, 16.dp, 0.dp)
warnings.lastIndex -> Modifier.padding(16.dp, 8.dp, 16.dp, 16.dp)
else -> Modifier.padding(16.dp, 8.dp, 16.dp, 0.dp)
}
Surface(
modifier = modifier.fillMaxWidth(),
shape = MaterialTheme.shapes.small,
color = colorResource(id = R.color.warning_warning),
elevation = 4.dp,
) {
Text(
modifier = Modifier.padding(16.dp),
text = warningConverter.convertError(item),
color = colorResource(id = R.color.lightGray0),
fontSize = 14.sp
)
}
}
}
}
@Composable
private fun AddButton(state: MutableState<AddCustomTokenState>) {
AddCustomTokenFab(
modifier = Modifier
.widthIn(210.dp, 280.dp),
isEnabled = state.value.screenState.addButton.isEnabled
) { domainStore.dispatch(AddCustomTokenAction.OnAddCustomTokenClicked) }
}
@Composable
fun AddCustomTokenFab(
modifier: Modifier = Modifier,
isEnabled: Boolean = true,
onClick: () -> Unit
) {
val contentColor = if (isEnabled) {
Color.White
} else {
colorResource(id = R.color.darkGray1)
}
val backgroundColor = Color(0xFF1ACE80)
ExtendedFloatingActionButton(
modifier = modifier,
icon = {
Icon(
imageVector = Icons.Filled.Add,
tint = contentColor,
contentDescription = "Add",
)
},
text = {
Text(
text = stringResource(id = R.string.common_add),
)
},
onClick = onClick,
backgroundColor = backgroundColor,
contentColor = contentColor,
)
}
data class ScreenFieldData(
val field: DataField<*>,
val error: AddCustomTokenError?,
val errorConverter: ErrorConverter<String>,
val viewState: ViewStates.TokenField
) {
companion object {
fun fromState(
field: DataField<*>,
state: AddCustomTokenState,
errorConverter: DomainErrorConverter
): ScreenFieldData {
return ScreenFieldData(
field = field,
error = state.getError(field.id),
errorConverter = errorConverter,
viewState = selectField(field.id, state.screenState)
)
}
private fun selectField(id: FieldId, screenState: ScreenState): ViewStates.TokenField {
return when (id) {
ContractAddress -> screenState.contractAddressField
Network -> screenState.network
Name -> screenState.name
Symbol -> screenState.symbol
Decimals -> screenState.decimals
DerivationPath -> screenState.derivationPath
else -> throw UnsupportedOperationException()
}
}
}
}

View file

@ -0,0 +1,245 @@
package com.tangem.tap.features.tokens.addCustomToken.compose
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.VoidCallback
import com.tangem.common.services.Result
import com.tangem.domain.common.form.Field
import com.tangem.domain.features.addCustomToken.TangemTechServiceManager
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
import com.tangem.domain.redux.domainStore
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.wallet.BuildConfig
import timber.log.Timber
/**
[REDACTED_AUTHOR]
*/
@Composable
fun AddCustomTokenDebugActions() {
if (!BuildConfig.DEBUG) return
Column() {
// deep test
// ActionRow("Invalid field value") { InvalidFieldValue() }
// ActionRow("Active(true)") { ActiveTrue() }
// ActionRow("Active(false) && decimalCount != null") { ActiveFalseDecimals() }
// ActionRow("In several networks") { InSeveralNetworks() }
// ActionRow("Address not found") { UnknownContracts() }
// test
ActionRow("All in one") { AllInOne() }
// Any action
// ActionRow("CustomActions - find tokens active=false, decimals != null") { CustomActions() }
}
}
@Composable
private fun AllInOne() {
// validation error
ContractAddressButton(
name = "invalid",
address = "unk"
)
// active = true
ContractAddressButton(
name = "true",
address = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
)
// active = false, decimalCount != null
ContractAddressButton(
name = "false",
address = "0x2147efff675e4a4ee1c2f918d181cdbd7a8e208f"
)
// more than one network
ContractAddressButton(
name = ">1 network",
address = "0xa1faa113cbe53436df28ff0aee54275c13b40975"
)
// unknown
ContractAddressButton(
name = "unk",
address = "0x1111111111111111112111111111111111111113"
)
}
@Composable
private fun InvalidFieldValue() {
ContractAddressButton(
name = "unk",
address = "unk"
)
ContractAddressButton(
name = "someText",
address = "someText"
)
ContractAddressButton(
name = "alskdml...fa s",
address = "alskdmlasd asdln alsdknflasd flasd fa s"
)
}
@Composable
private fun ActiveTrue() {
ContractAddressButton(
name = "USDC- Ethereum",
address = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
)
ContractAddressButton(
name = "USDT- Avalanche",
address = "0xc7198437980c041c805a1edcba50c1ce5db95118"
)
ContractAddressButton(
name = "VID- Fantom",
address = "0x922d641a426dcffaef11680e5358f34d97d112e1"
)
}
@Composable
private fun ActiveFalseDecimals() {
ContractAddressButton(
name = "ALPHA- avalanche",
address = "0x2147efff675e4a4ee1c2f918d181cdbd7a8e208f"
)
ContractAddressButton(
name = "BETA- avalanche",
address = "0x511d35c52a3c244e7b8bd92c0c297755fbd89212"
)
}
@Composable
private fun InSeveralNetworks() {
ContractAddressButton(
name = "ALPHA- Eth,Bsc",
address = "0xa1faa113cbe53436df28ff0aee54275c13b40975"
)
ContractAddressButton(
name = "BETA- Eth,Bsc",
address = "0xbe1a001fe942f96eea22ba08783140b9dcc09d28"
)
}
@Composable
private fun UnknownContracts() {
ContractAddressButton(
name = "0x11...113",
address = "0x1111111111111111112111111111111111111113"
)
ContractAddressButton(
name = "0xc7...111",
address = "0xc7198437980c041c805a1edcba50c1ce5db95111"
)
}
@Composable
private fun CustomActions() {
CustomActionButton(
name = "Find tokens in several networks",
action = {
val manager = TangemTechServiceManager(TangemTechService())
val currencies = manager.tokens()
val asdfsd = mutableMapOf<String, MutableList<Any>>()
val contractAddresses = currencies.mapNotNull { currency ->
currency.contracts?.map { it.address }
}.flatten()
contractAddresses.take(500).forEachIndexed() { index, address ->
when (val result = manager.checkAddress(address)) {
is Result.Success -> {
val contractList = mutableListOf<Any>()
result.data.forEach { token ->
token.contracts.forEach { contract ->
if (!contract.active && contract.decimalCount != null) {
contractList.add(contract)
}
}
}
if (contractList.isNotEmpty()) {
val list = asdfsd[address] ?: mutableListOf()
list.addAll(contractList)
asdfsd[address] = list
}
Timber.e("Success. handle $index item from size ${contractAddresses.size}. Result = ${asdfsd.size}")
}
is Result.Failure -> {}
}
}
val result = asdfsd.filter { it.value.size > 1 }
if (result.isEmpty()) return@CustomActionButton
}
)
}
@Composable
private fun ActionRow(
name: String,
content: @Composable () -> Unit
) {
Column() {
Text(
modifier = Modifier.padding(horizontal = 16.dp),
text = name,
fontSize = 14.sp
)
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Start
) { content() }
}
}
@Composable
private fun ActionButton(
name: String,
onClick: VoidCallback,
) {
Button(
modifier = Modifier.padding(horizontal = 4.dp),
onClick = onClick
) { Text(name, fontSize = 8.sp) }
}
@Composable
private fun ContractAddressButton(
name: String,
address: String,
) {
ActionButton(name = name) {
resetTokenValues()
domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data(address, false)))
}
}
@Composable
private fun CustomActionButton(
name: String,
action: suspend () -> Unit
) {
val startValue = 0
val anyValue = remember { mutableStateOf(startValue) }
LaunchedEffect(key1 = anyValue.value, block = {
if (anyValue.value != startValue) {
action()
}
})
ActionButton(name) { anyValue.value = anyValue.value + 1 }
}
private fun resetTokenValues() {
domainStore.dispatch(AddCustomTokenAction.OnTokenNetworkChanged(Field.Data(Blockchain.Unknown, false)))
// domainStore.dispatch(AddCustomTokenAction.OnTokenNameChanged(Field.Data("", false)))
// domainStore.dispatch(AddCustomTokenAction.OnTokenSymbolChanged(Field.Data("", false)))
// domainStore.dispatch(AddCustomTokenAction.OnTokenDecimalsChanged(Field.Data("", false)))
}

View file

@ -0,0 +1,134 @@
package com.tangem.tap.features.tokens.addCustomToken.compose
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import com.tangem.domain.common.form.Field
import com.tangem.domain.features.addCustomToken.TokenBlockchainField
import com.tangem.domain.features.addCustomToken.TokenDerivationPathField
import com.tangem.domain.features.addCustomToken.TokenField
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
import com.tangem.domain.redux.domainStore
import com.tangem.tap.common.compose.BlockchainSpinner
import com.tangem.tap.common.compose.OutlinedTextFieldWidget
import com.tangem.tap.common.compose.SpacerH8
import com.tangem.tap.common.compose.TitleSubtitle
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
@Composable
fun TokenContractAddressView(screenFieldData: ScreenFieldData) {
if (!screenFieldData.viewState.isVisible) return
val tokenField = screenFieldData.field as TokenField
OutlinedTextFieldWidget(
textFieldData = tokenField.data,
labelId = R.string.custom_token_contract_address_input_title,
placeholder = "0x0000000000000000",
isEnabled = screenFieldData.viewState.isEnabled,
isLoading = screenFieldData.viewState.isLoading,
error = screenFieldData.error,
errorConverter = screenFieldData.errorConverter,
) {
domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data(it)))
}
SpacerH8()
}
@Composable
fun TokenNameView(screenFieldData: ScreenFieldData) {
if (!screenFieldData.viewState.isVisible) return
val tokenField = screenFieldData.field as TokenField
OutlinedTextFieldWidget(
textFieldData = tokenField.data,
labelId = R.string.custom_token_name_input_title,
placeholderId = R.string.custom_token_name_input_placeholder,
isEnabled = screenFieldData.viewState.isEnabled,
error = screenFieldData.error,
errorConverter = screenFieldData.errorConverter,
) {
domainStore.dispatch(AddCustomTokenAction.OnTokenNameChanged(Field.Data(it)))
}
SpacerH8()
}
@Composable
fun TokenNetworkView(screenFieldData: ScreenFieldData, state: AddCustomTokenState) {
if (!screenFieldData.viewState.isVisible) return
val notSelected = stringResource(id = R.string.custom_token_network_input_not_selected)
val networkField = screenFieldData.field as TokenBlockchainField
BlockchainSpinner(
title = R.string.custom_token_network_input_title,
itemList = networkField.itemList,
selectedItem = networkField.data,
isEnabled = screenFieldData.viewState.isEnabled,
textFieldConverter = { state.convertBlockchainName(it, notSelected) },
) { domainStore.dispatch(AddCustomTokenAction.OnTokenNetworkChanged(Field.Data(it))) }
SpacerH8()
}
@Composable
fun TokenSymbolView(screenFieldData: ScreenFieldData) {
if (!screenFieldData.viewState.isVisible) return
val tokenField = screenFieldData.field as TokenField
OutlinedTextFieldWidget(
textFieldData = tokenField.data,
labelId = R.string.custom_token_token_symbol_input_title,
placeholderId = R.string.custom_token_token_symbol_input_placeholder,
isEnabled = screenFieldData.viewState.isEnabled,
error = screenFieldData.error,
errorConverter = screenFieldData.errorConverter,
) { domainStore.dispatch(AddCustomTokenAction.OnTokenSymbolChanged(Field.Data(it))) }
SpacerH8()
}
@Composable
fun TokenDecimalsView(screenFieldData: ScreenFieldData) {
if (!screenFieldData.viewState.isVisible) return
val tokenField = screenFieldData.field as TokenField
OutlinedTextFieldWidget(
textFieldData = tokenField.data,
labelId = R.string.custom_token_decimals_input_title,
placeholder = "8",
isEnabled = screenFieldData.viewState.isEnabled,
error = screenFieldData.error,
errorConverter = screenFieldData.errorConverter,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
) { domainStore.dispatch(AddCustomTokenAction.OnTokenDecimalsChanged(Field.Data(it))) }
SpacerH8()
}
@Composable
fun TokenDerivationPathView(screenFieldData: ScreenFieldData, state: AddCustomTokenState) {
if (!screenFieldData.viewState.isVisible) return
val notSelected = stringResource(id = R.string.custom_token_derivation_path_default)
val networkField = screenFieldData.field as TokenDerivationPathField
BlockchainSpinner(
title = R.string.custom_token_derivation_path_input_title,
itemList = networkField.itemList,
selectedItem = networkField.data,
isEnabled = screenFieldData.viewState.isEnabled,
textFieldConverter = { state.convertBlockchainName(it, notSelected) },
dropdownItemView = { blockchain ->
val derivationPathLabel = state.convertDerivationPathLabel(blockchain, notSelected)
val blockchainName = state.convertBlockchainName(blockchain, notSelected)
TitleSubtitle(derivationPathLabel, blockchainName)
}
) { domainStore.dispatch(AddCustomTokenAction.OnTokenDerivationPathChanged(Field.Data(it))) }
SpacerH8()
}

View file

@ -0,0 +1,37 @@
package com.tangem.tap.features.tokens.addCustomToken.compose
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.tangem_sdk_new.extensions.pxToDp
import com.tangem.tap.common.compose.Keyboard
/**
[REDACTED_AUTHOR]
*/
@Composable
fun HangingOverKeyboardView(
modifier: Modifier = Modifier,
keyboardState: State<Keyboard>,
spaceBetweenKeyboard: Dp = 0.dp,
content: @Composable() (BoxScope.() -> Unit)
) {
val context = LocalContext.current
val padding = when (keyboardState.value) {
Keyboard.Closed -> 0.dp
is Keyboard.Opened -> {
val keyboardHeight = (keyboardState.value as Keyboard.Opened).height
val keyboardPadding = context.pxToDp(keyboardHeight.toFloat()).dp
keyboardPadding + spaceBetweenKeyboard
}
}
Box(modifier.padding(bottom = padding)) { content() }
}

View file

@ -0,0 +1,21 @@
package com.tangem.tap.features.tokens.addCustomToken.compose
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.tangem.domain.DomainDialog
import com.tangem.tap.common.compose.SimpleDialog
import com.tangem.tap.common.compose.TitleSubtitle
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
@Composable
fun SelectTokenNetworkDialog(dialog: DomainDialog.SelectTokenDialog, onDismissRequest: () -> Unit) {
SimpleDialog(
title = stringResource(id = R.string.custom_token_type_network),
items = dialog.items,
onSelect = dialog.onSelect,
onDismissRequest = onDismissRequest
) { contract -> TitleSubtitle(dialog.networkIdConverter(contract.networkId), contract.address) }
}

View file

@ -2,7 +2,7 @@ package com.tangem.tap.features.tokens.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.features.wallet.redux.WalletData
import org.rekotlin.Action
@ -30,4 +30,6 @@ sealed class TokensAction : Action {
val addedTokens: List<TokenWithBlockchain>,
val addedBlockchains: List<Blockchain>
) : TokensAction()
object PrepareAndNavigateToAddCustomToken : TokensAction()
}

View file

@ -8,20 +8,25 @@ import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.toMapKey
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.domain.DomainWrapped
import com.tangem.domain.common.KeyWalletPublicKey
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.features.addCustomToken.CompleteData
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
import com.tangem.domain.features.addCustomToken.redux.AddedCurrencies
import com.tangem.domain.redux.domainStore
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.tap.*
import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.tasks.product.KeyWalletPublicKey
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.scope
@ -31,6 +36,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
import timber.log.Timber
class TokensMiddleware {
@ -40,6 +46,7 @@ class TokensMiddleware {
when (action) {
is TokensAction.LoadCurrencies -> handleLoadCurrencies(action)
is TokensAction.SaveChanges -> handleSaveChanges(action)
is TokensAction.PrepareAndNavigateToAddCustomToken -> handleAddingCustomToken(action)
}
next(action)
}
@ -88,6 +95,22 @@ class TokensMiddleware {
}
}
private fun handleAddingCustomToken(action: TokensAction.PrepareAndNavigateToAddCustomToken) {
val tokensState = store.state.tokensState
val addedTokensList = tokensState.addedTokens.map {
DomainWrapped.TokenWithBlockchain(it.token.copy(), it.blockchain)
}
val addedBlockchains = tokensState.addedBlockchains.map { it }
val addedCurrencies = AddedCurrencies(addedTokensList, addedBlockchains)
domainStore.dispatch(AddCustomTokenAction.Init.SetAddedCurrencies(addedCurrencies))
val callback = fun(data: CompleteData) {
Timber.e("Yoooohhhoooo")
}
domainStore.dispatch(AddCustomTokenAction.Init.SetOnAddTokenCallback(callback))
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomToken))
}
private fun deriveMissingBlockchains(
scanResponse: ScanResponse,
blockchains: List<Blockchain>,

View file

@ -3,9 +3,9 @@ package com.tangem.tap.features.tokens.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.Token
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.domain.tokens.fromNetworkId
import com.tangem.tap.features.wallet.redux.WalletData
import org.rekotlin.StateType

View file

@ -104,6 +104,10 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.menu_search -> true
R.id.menu_navigate_add_custom_token -> {
store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken)
true
}
else -> super.onOptionsItemSelected(item)
}
}

View file

@ -16,10 +16,10 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.SubcomposeAsyncImage
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.tap.common.extensions.getGreyedOutIconRes
import com.tangem.tap.common.extensions.getRoundIconRes
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.domain.tokens.fromNetworkId
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
import com.tangem.wallet.R

View file

@ -17,12 +17,12 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.tap.common.compose.Keyboard
import com.tangem.tap.common.compose.keyboardAsState
import com.tangem.tap.common.extensions.pixelsToDp
import com.tangem.tap.domain.TapWorkarounds.useOldStyleDerivation
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.domain.tokens.fromNetworkId
import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
import com.tangem.tap.features.tokens.redux.TokensState

View file

@ -18,8 +18,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.SubcomposeAsyncImage
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.domain.tokens.fromNetworkId
import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
import com.tangem.wallet.R

View file

@ -5,12 +5,12 @@ import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchain.extensions.isAboveZero
import com.tangem.common.extensions.isZero
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.tap.common.entities.Button
import com.tangem.tap.common.extensions.toQrCode
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.common.toggleWidget.WidgetState
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.extensions.buyIsAllowed
import com.tangem.tap.domain.extensions.sellIsAllowed

View file

@ -9,6 +9,7 @@ import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.isZero
import com.tangem.common.services.Result
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.operations.attestation.Attestation
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.common.analytics.Analytics

View file

@ -6,17 +6,17 @@ import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.card.Card
import com.tangem.common.card.FirmwareVersion
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.extensions.isGreaterThan
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.extensions.getSingleWallet
import com.tangem.tap.domain.extensions.hasSignedHashes
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
import com.tangem.tap.domain.extensions.remainingSignatures
import com.tangem.tap.domain.isMultiwalletAllowed
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.NetworkConnectivity

View file

@ -3,6 +3,7 @@ package com.tangem.tap.features.wallet.redux.reducers
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Wallet
import com.tangem.domain.common.TwinCardNumber
import com.tangem.tap.common.extensions.toFiatString
import com.tangem.tap.common.extensions.toFiatValue
import com.tangem.tap.common.extensions.toFormattedCurrencyString
@ -13,7 +14,6 @@ import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.getArtworkUrl
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.tap.features.wallet.redux.*
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData

View file

@ -8,10 +8,10 @@ import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.loadCurrenciesIcon
import com.tangem.tap.common.extensions.show
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletData

View file

@ -3,14 +3,14 @@ package com.tangem.tap.features.wallet.ui.wallet
import android.app.Dialog
import androidx.recyclerview.widget.LinearLayoutManager
import com.tangem.common.card.Card
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletDialog

View file

@ -5,12 +5,12 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.recyclerview.widget.LinearLayoutManager
import com.tangem.domain.common.TwinCardNumber
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.fitChipsByGroupWidth
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.redux.*

View file

@ -1,62 +0,0 @@
package com.tangem.tap.network
import com.squareup.moshi.FromJson
import com.squareup.moshi.Moshi
import com.squareup.moshi.ToJson
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.common.json.TangemSdkAdapter
import com.tangem.wallet.BuildConfig
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import java.math.BigDecimal
import java.util.concurrent.TimeUnit
fun createRetrofitInstance(
baseUrl: String,
interceptors: List<Interceptor> = emptyList(),
): Retrofit {
val okHttpBuilder = OkHttpClient.Builder()
interceptors.forEach { okHttpBuilder.addInterceptor(it) }
addTimeOuts(okHttpBuilder)
if (BuildConfig.DEBUG) okHttpBuilder.addInterceptor(createHttpLoggingInterceptor())
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(createMoshiConverterFactory())
.client(okHttpBuilder.build())
.build()
}
private fun addTimeOuts(okHttpBuilder: OkHttpClient.Builder) {
okHttpBuilder.callTimeout(1, TimeUnit.SECONDS)
okHttpBuilder.connectTimeout(20, TimeUnit.SECONDS)
okHttpBuilder.readTimeout(20, TimeUnit.SECONDS)
okHttpBuilder.writeTimeout(20, TimeUnit.SECONDS)
}
fun createMoshiConverterFactory(): Converter.Factory = MoshiConverterFactory.create(createMoshi())
fun createMoshi(): Moshi = Moshi.Builder()
.add(BigDecimalAdapter)
.add(KotlinJsonAdapterFactory())
.add(TangemSdkAdapter.DerivationPathAdapter())
.add(TangemSdkAdapter.DerivationNodeAdapter())
.build()
private fun createHttpLoggingInterceptor(): HttpLoggingInterceptor {
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.BODY
return logging
}
private object BigDecimalAdapter {
@FromJson
fun fromJson(string: String) = BigDecimal(string)
@ToJson
fun toJson(value: BigDecimal) = value.toString()
}

View file

@ -1,8 +1,7 @@
package com.tangem.tap.network.coinmarketcap
import com.tangem.tap.network.createRetrofitInstance
import okhttp3.Interceptor
import okhttp3.Response
import com.tangem.network.common.AddHeaderInterceptor
import com.tangem.network.common.createRetrofitInstance
import retrofit2.http.GET
import retrofit2.http.Query
@ -10,9 +9,9 @@ interface CoinMarketCapApi {
@GET("v1/tools/price-conversion")
suspend fun getRateInfo(
@Query("amount") amount: Int,
@Query("symbol") cryptoCurrencyName: String,
@Query("convert") fiatCurrencyName: String? = null
@Query("amount") amount: Int,
@Query("symbol") cryptoCurrencyName: String,
@Query("convert") fiatCurrencyName: String? = null
): RateInfoResponse
@GET("v1/fiat/map")
@ -24,19 +23,11 @@ interface CoinMarketCapApi {
fun create(apiKey: String): CoinMarketCapApi {
return createRetrofitInstance(
baseUrl,
listOf(createCoinMarketRequestInterceptor(apiKey)),
baseUrl = baseUrl,
interceptors = listOf(
AddHeaderInterceptor(mapOf("X-CMC_PRO_API_KEY" to apiKey)),
),
).create(CoinMarketCapApi::class.java)
}
}
}
private fun createCoinMarketRequestInterceptor(apiKey: String): Interceptor {
return object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val requestBuilder = chain.request().newBuilder()
requestBuilder.addHeader("X-CMC_PRO_API_KEY", apiKey)
return chain.proceed(requestBuilder.build())
}
}
}

View file

@ -5,9 +5,9 @@ import android.util.Base64
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.network.common.createRetrofitInstance
import com.tangem.tap.common.extensions.urlEncode
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.network.createRetrofitInstance
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.ExchangeService
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder

View file

@ -1,8 +1,6 @@
package com.tangem.tap.network.exchangeServices.onramper
import com.squareup.moshi.JsonClass
import okhttp3.Interceptor
import okhttp3.Response
import retrofit2.http.GET
import retrofit2.http.Path
@ -23,15 +21,6 @@ interface OnramperApi {
}
}
class AddKeyToHeaderInterceptor(
private val key: String
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder().addHeader("Authorization", "Basic $key").build()
return chain.proceed(request)
}
}
@JsonClass(generateAdapter = true)
data class GatewaysResponse(
val gateways: List<OnramperGateway>

View file

@ -4,9 +4,10 @@ import android.net.Uri
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.network.common.AddHeaderInterceptor
import com.tangem.network.common.createRetrofitInstance
import com.tangem.tap.common.extensions.urlEncode
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.network.createRetrofitInstance
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.ExchangeService
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
@ -23,10 +24,12 @@ class OnramperService(
) : ExchangeService, ExchangeUrlBuilder {
private val api: OnramperApi by lazy {
createRetrofitInstance(OnramperApi.BASE_URL,
listOf(AddKeyToHeaderInterceptor(apiKey))
)
.create(OnramperApi::class.java)
createRetrofitInstance(
baseUrl = OnramperApi.BASE_URL,
interceptors = listOf(
AddHeaderInterceptor(mapOf("Authorization" to "Basic $apiKey")),
)
).create(OnramperApi::class.java)
}
private var status: OnramperStatus? = null

View file

@ -3,7 +3,7 @@ package com.tangem.tap.network.payid
import com.squareup.moshi.JsonClass
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.tap.network.createRetrofitInstance
import com.tangem.network.common.createRetrofitInstance
import retrofit2.Retrofit
class PayIdService {

View file

@ -2,7 +2,7 @@ package com.tangem.tap.network.payid
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.tap.network.createRetrofitInstance
import com.tangem.network.common.createRetrofitInstance
/**
[REDACTED_AUTHOR]

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.compose.ui.platform.ComposeView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/view_compose"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinator_wallet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundLightGray"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
style="@style/Widget.MaterialComponents.Toolbar.Surface"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/backgroundLightGray"
android:fitsSystemWindows="true"
app:liftOnScroll="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="@drawable/ic_baseline_arrow_back_24" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/view_compose"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -9,4 +9,11 @@
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always" />
<item
android:id="@+id/menu_navigate_add_custom_token"
android:icon="@drawable/ic_add"
android:title="@string/common_add"
app:iconTint="@color/darkGray2"
app:showAsAction="always" />
</menu>

View file

@ -187,7 +187,6 @@
<string name="warning_button_can_be_better">Может быть лучше</string>
<string name="warning_rate_app_title">Один вопрос</string>
<string name="warning_rate_app_message">Вам нравится приложение Tangem?</string>
<string name="custom_token_creation_error_empty_field">Все поля обязательны к заполнению</string>
<string name="manage_tokens_no_tokens">Токенов пока нет</string>
<string name="manage_tokens_section_title_added_tokens">Добавленные токены</string>
<string name="manage_tokens_alert_remove_token_title">Удалить токен %s</string>
@ -197,20 +196,31 @@
<string name="add_token_search_hint">Начните поиск</string>
<string name="manage_tokens_title">Управление токенами</string>
<string name="add_custom">Добавить пользовательский</string>
<string name="add_token_section_title_custom_tokens">Пользовательские токены</string>
<string name="add_token_section_title_popular_tokens">Популярные токены</string>
<string name="add_custom_token_title">Добавить пользовательский токен</string>
<string name="custom_token_creation_error_empty_fields">Пожалуйста заполните все поля</string>
<string name="custom_token_creation_error_wrong_decimals">Введенное число не является допустимым десятичным числом</string>
<string name="custom_token_name_input_title">Имя</string>
<string name="custom_token_token_symbol_input_title">Символ токена</string>
<string name="custom_token_contract_address_input_title">Адрес контракта</string>
<string name="custom_token_decimals_input_title">Десятичные</string>
<string name="custom_token_name_input_placeholder">бывший USD Coin</string>
<string name="custom_token_token_symbol_input_placeholder">бывший USDC</string>
<string name="add_token_added">Добавлен</string>
<string name="remove_token">Удалить токен</string>
<string name="token_icon">Значок токена</string>
<string name="add_custom_token_title" translatable="false">Добавить токен</string>
<string name="custom_token_contract_address_input_title">Адрес контракта</string>
<string name="custom_token_creation_error_network_not_selected">Пожалуйста, выберите сеть</string>
<string name="custom_token_creation_error_empty_fields">Пожалуйста, заполните все поля</string>
<string name="custom_token_creation_error_wrong_decimals">Количество знаков после запятой должно быть корректным числом не больше %d</string>
<string name="custom_token_creation_error_invalid_contract_address">Адрес контракта некорректен</string>
<string name="custom_token_creation_error_invalid_derivation_path">Путь деривации некорректен</string>
<string name="custom_token_validation_error_not_found">Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить</string>
<string name="custom_token_validation_error_already_added">Этот токен/сеть уже находится в вашем списке</string>
<string name="custom_token_type_network">Сеть</string>
<string name="custom_token_type_token">Токен</string>
<string name="custom_token_decimals_input_title">Знаков после запятой</string>
<string name="custom_token_network_input_title">Сеть</string>
<string name="custom_token_network_input_not_selected">Не выбрано</string>
<string name="custom_token_name_input_placeholder">Например, USD Coin</string>
<string name="custom_token_name_input_title">Название токена</string>
<string name="custom_token_token_symbol_input_placeholder">Например, USDC</string>
<string name="custom_token_token_symbol_input_title">Символ токена</string>
<string name="custom_token_derivation_path_input_title">Деривация по BIP44</string>
<string name="custom_token_derivation_path_default">По-умолчанию</string>
<string name="custom_token_add_token">Добавить токен</string>
<string name="details_section_title_blockchain">Блокчейн</string>
<string name="details_row_title_manage_tokens">Управление токенами</string>
<string name="add_tokens_subtitle_blockchains">Блокчейны</string>

View file

@ -17,8 +17,17 @@
<color name="warning">#FFB71B</color>
<color name="white">#FFFFFF</color>
<color name="darkGray1">#8E8E93</color>
<color name="darkGray2">#636366</color>
<color name="lightGray0">#F3F3F3</color>
<color name="lightGray1">#F8F8FB</color>
<color name="lightGray2">#DADADF</color>
<color name="lightGray3">#D0D0D5</color>
<color name="lightGray4">#C7C7CC</color>
<color name="lightGray5">#D1D1D6</color>
<color name="lightGray6">#CACACC</color>
<color name="darkGray0">#B6B6B8</color>
<color name="darkGray1">#8E8E90</color>
<color name="darkGray2">#666668</color>
<color name="darkGray3">#48484A</color>
<color name="darkGray4">#3A3A3C</color>
<color name="darkGray5">#2C2C2E</color>
@ -37,14 +46,6 @@
<color name="twins_dark">#14181D</color>
<color name="lightGray0">#F3F3F3</color>
<color name="lightGray1">#F8F8FB</color>
<color name="lightGray2">#DADADF</color>
<color name="lightGray3">#D0D0D5</color>
<color name="lightGray4">#C7C7CC</color>
<color name="lightGray5">#D1D1D6</color>
<color name="lightGray6">#C9C9CD</color>
<color name="separatorGrey1">#1F000000</color>
<color name="separatorGrey2">#14212121</color>

Some files were not shown because too many files have changed in this diff Show more