Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-18 15:33:13 +03:00
commit f2e19e40be
35 changed files with 1034 additions and 370 deletions

View file

@ -37,5 +37,8 @@ fun createNetworkLoggingInterceptor(): Interceptor {
return LoggingInterceptor.Builder()
.setLevel(Level.BODY)
.log(Log.VERBOSE)
.tag(NETWORK_LOGS_TAG)
.build()
}
}
private const val NETWORK_LOGS_TAG = "NetworkLogs"

View file

@ -1,76 +1,67 @@
package com.tangem.core.ui.components.transactions.state
import androidx.paging.PagingData
import androidx.paging.TerminalSeparatorType
import androidx.paging.insertHeaderItem
import com.tangem.core.ui.components.wallet.WalletLockedContentState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/**
* Wallet transaction history state
*/
/** Wallet transaction history state */
sealed interface TxHistoryState {
/**
* Wallet transaction history state with content
* Wallet transaction history state with content. Items contains a required [TxHistoryItemState.Title].
*
* @property items content items
* @property contentItems content items
*/
sealed class ContentState(open val items: Flow<PagingData<TxHistoryItemState>>) : TxHistoryState
sealed class ContentState(private val contentItems: Flow<PagingData<TxHistoryItemState>>) : TxHistoryState {
/** Lambda be invoke when explore button was clicked */
abstract val onExploreClick: () -> Unit
/** Content items with [TxHistoryItemState.Title] */
val items: Flow<PagingData<TxHistoryItemState>>
get() {
return contentItems.map {
it.insertHeaderItem(
terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE,
item = TxHistoryItemState.Title(onExploreClick = onExploreClick),
)
}
}
}
/**
* Loading state
*
* @property onExploreClick lambda be invoke when explore button was clicked
* @property transactions loading transactions
*/
data class Loading(val onExploreClick: () -> Unit) : ContentState(
items = flowOf(
PagingData.from(
listOf(
TxHistoryItemState.Title(onExploreClick = onExploreClick),
TxHistoryItemState.Transaction(state = TransactionState.Loading(txHash = LOADING_TX_HASH)),
),
),
),
)
/**
* Wallet transaction history state with loading transactions
*
* @property itemsCount count of loading transactions
*/
data class ContentWithLoadingItems(val itemsCount: Int) : ContentState(
items = flowOf(
value = PagingData.from(
data = buildList(capacity = itemsCount) {
add(TxHistoryItemState.Transaction(state = TransactionState.Loading(txHash = LOADING_TX_HASH)))
},
),
),
)
data class Loading(
override val onExploreClick: () -> Unit,
val transactions: Flow<PagingData<TxHistoryItemState>> = getDefaultLoadingTransactions(),
) : ContentState(transactions)
/**
* Wallet transaction history state with content
*
* @property items content items
* @property onExploreClick lambda be invoke when explore button was clicked
* @property contentItems content items
*/
data class Content(override val items: Flow<PagingData<TxHistoryItemState>>) : ContentState(items)
data class Content(
override val onExploreClick: () -> Unit,
val contentItems: Flow<PagingData<TxHistoryItemState>>,
) : ContentState(contentItems)
/**
* Locked state
*
* @property onExploreClick lambda be invoke when explore button was clicked
*/
data class Locked(val onExploreClick: () -> Unit) :
ContentState(
items = flowOf(
PagingData.from(
listOf(
TxHistoryItemState.Title(onExploreClick = onExploreClick),
TxHistoryItemState.Transaction(state = TransactionState.Loading(txHash = LOADING_TX_HASH)),
),
),
),
),
data class Locked(override val onExploreClick: () -> Unit) :
ContentState(contentItems = getDefaultLoadingTransactions()),
WalletLockedContentState
/**
@ -121,5 +112,17 @@ sealed interface TxHistoryState {
private companion object {
const val LOADING_TX_HASH = "LOADING_TX_HASH"
private fun getDefaultLoadingTransactions(): Flow<PagingData<TxHistoryItemState>> {
return flowOf(
value = PagingData.from(
data = listOf(
element = TxHistoryItemState.Transaction(
state = TransactionState.Loading(txHash = LOADING_TX_HASH),
),
),
),
)
}
}
}

View file

@ -20,12 +20,19 @@ sealed interface TextReference {
* Text resource id
*
* @property id resource id
* @property formatArgs arguments
*
* Impossible to use [kotlinx.collections.immutable.ImmutableList] because [Any] is unstable.
* @property formatArgs arguments. Impossible to use [kotlinx.collections.immutable.ImmutableList] because [Any] is
* unstable.
*/
data class Res(@StringRes val id: Int, val formatArgs: WrappedList<Any> = WrappedList(emptyList())) : TextReference
/**
* Plural resource id
*
* @property id resource id
* @property count count
* @property formatArgs arguments. Impossible to use [kotlinx.collections.immutable.ImmutableList] because [Any] is
* unstable.
*/
data class PluralRes(@PluralsRes val id: Int, val count: Int, val formatArgs: WrappedList<Any>) : TextReference
/**
@ -34,9 +41,16 @@ sealed interface TextReference {
* @property value value
*/
data class Str(val value: String) : TextReference
/**
* Combined reference. It concatenates all [refs].
*
* @see [TextReference.plus] method
*/
data class Combined(val refs: WrappedList<TextReference>) : TextReference
}
/** Get text */
/** Resolve [TextReference] as [String] */
@Composable
@ReadOnlyComposable
fun TextReference.resolveReference(): String {
@ -44,5 +58,23 @@ fun TextReference.resolveReference(): String {
is TextReference.Res -> stringResource(id, *formatArgs.toTypedArray())
is TextReference.PluralRes -> pluralStringResource(id, count, *formatArgs.toTypedArray())
is TextReference.Str -> value
is TextReference.Combined -> {
buildString {
refs.forEach {
append(it.resolveReference())
}
}
}
}
}
/** Concatenate [this] reference with [ref] */
operator fun TextReference.plus(ref: TextReference): TextReference {
return when (this) {
is TextReference.Combined -> copy(refs = (refs.data + ref).toWrappedList())
is TextReference.PluralRes,
is TextReference.Res,
is TextReference.Str,
-> TextReference.Combined(refs = wrappedList(this, ref))
}
}

View file

@ -7,4 +7,8 @@ import androidx.compose.runtime.Immutable
*/
@JvmInline
@Immutable
value class WrappedList<T>(val data: List<T>) : List<T> by data
value class WrappedList<T>(val data: List<T>) : List<T> by data
fun <T> List<T>.toWrappedList(): WrappedList<T> = WrappedList(data = this)
fun <T> wrappedList(vararg elements: T): WrappedList<T> = WrappedList(data = listOf(*elements))

View file

@ -12,22 +12,14 @@ object BigDecimalFormatter {
private const val TEMP_CURRENCY_CODE = "USD"
fun formatCryptoAmount(
cryptoAmount: BigDecimal,
cryptoCurrency: String,
decimals: Int,
locale: Locale = Locale.getDefault(),
): String {
val formatterCurrency = getCurrency(cryptoCurrency)
val formatter = NumberFormat.getCurrencyInstance(locale).apply {
currency = formatterCurrency
fun formatCryptoAmount(cryptoAmount: BigDecimal, cryptoCurrency: String, decimals: Int): String {
val formatter = NumberFormat.getNumberInstance().apply {
maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8)
minimumFractionDigits = 2
roundingMode = RoundingMode.DOWN
}
return formatter.format(cryptoAmount)
.replace(formatterCurrency.getSymbol(locale), cryptoCurrency)
return formatter.format(cryptoAmount) + "\u2009$cryptoCurrency"
}
fun formatFiatAmount(