Updated on 2026-08-14
This commit is contained in:
parent
9a8ff1830d
commit
a0798cfef2
252 changed files with 247 additions and 5857 deletions
|
|
@ -20,7 +20,7 @@ class GlobalLayoutStateHandler<T : View>(
|
|||
if (attachImmediately) attach()
|
||||
}
|
||||
|
||||
fun attach() {
|
||||
private fun attach() {
|
||||
if (isAttached) {
|
||||
Timber.d("Already attached")
|
||||
return
|
||||
|
|
|
|||
|
|
@ -11,20 +11,6 @@ fun postUi(ms: Long = 0, func: Runnable) {
|
|||
if (ms == 0L) uiHandler.post { func.run() } else uiHandler.postDelayed(func, ms)
|
||||
}
|
||||
|
||||
fun postBackground(ms: Long = 0, func: Runnable) {
|
||||
if (ms == 0L) backgroundHandler.post { func.run() } else backgroundHandler.postDelayed(func, ms)
|
||||
}
|
||||
|
||||
fun postUiDelayBg(ms: Long, func: Runnable) {
|
||||
backgroundHandler.postDelayed({ uiHandler.post(func) }, ms)
|
||||
}
|
||||
|
||||
fun post(ms: Long = 0, func: Runnable) {
|
||||
if (ms == 0L) {
|
||||
func.run()
|
||||
} else {
|
||||
val currentLooper = Looper.myLooper() ?: return
|
||||
|
||||
Handler(currentLooper).postDelayed(func, ms)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
package com.tangem.tap.common
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
open class ShimmerRecyclerAdapter(
|
||||
private val viewHolderViewFactory: (ViewGroup) -> ViewGroup,
|
||||
) : ListAdapter<ShimmerData, ShimmerVH>(DiffUtilCallback) {
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return if (currentList.isEmpty()) 0 else currentList[position].hashCode().toLong()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShimmerVH {
|
||||
return ShimmerVH(viewHolderViewFactory.invoke(parent))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ShimmerVH, position: Int) {}
|
||||
|
||||
object DiffUtilCallback : DiffUtil.ItemCallback<ShimmerData>() {
|
||||
override fun areContentsTheSame(oldItem: ShimmerData, newItem: ShimmerData) = oldItem == newItem
|
||||
override fun areItemsTheSame(oldItem: ShimmerData, newItem: ShimmerData) = oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
class ShimmerVH(viewGroup: ViewGroup) : RecyclerView.ViewHolder(viewGroup)
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
data class ShimmerData(private val any: String = "")
|
||||
|
|
@ -8,11 +8,8 @@ import androidx.appcompat.widget.LinearLayoutCompat
|
|||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.BuildConfig
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -22,18 +19,6 @@ object TestActions {
|
|||
|
||||
// It used only for the test actions in debug or debug_beta builds
|
||||
var testAmountInjectionForWalletManagerEnabled = false
|
||||
|
||||
/**
|
||||
* @param isTestView - true must be used if you want to show or hide your view depends on BuildConfig
|
||||
*/
|
||||
fun initFor(view: View, actions: List<TestAction>, isTestView: Boolean = false) {
|
||||
if (!BuildConfig.TEST_ACTION_ENABLED) return
|
||||
if (isTestView) view.show(BuildConfig.TEST_ACTION_ENABLED)
|
||||
|
||||
view.setOnClickListener {
|
||||
store.dispatchDialogShow(AppDialog.TestActionsDialog(actions))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias TestAction = Pair<String, () -> Unit>
|
||||
|
|
|
|||
|
|
@ -22,10 +22,6 @@ class AnalyticsFactory {
|
|||
filters.add(filter)
|
||||
}
|
||||
|
||||
fun addParamsInterceptor(interceptor: ParamsInterceptor) {
|
||||
interceptors.add(interceptor)
|
||||
}
|
||||
|
||||
fun build(analytics: Analytics, data: AnalyticsHandlerBuilder.Data) {
|
||||
builders.mapNotNull { it.build(data) }.forEach { analytics.addHandler(it.id(), it) }
|
||||
filters.forEach { analytics.addFilter(it) }
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.tangem.tap.common.analytics.converters
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.features.demo.DemoTransactionSender
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.tap.common.analytics.converters
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.tap.common.analytics.converters
|
||||
|
||||
import com.shopify.buy3.Storefront
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.tap.common.analytics.events.Shop
|
||||
import com.tangem.tap.features.shop.domain.models.ProductType
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.converters
|
||||
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class TopUpEventConverter : Converter<Pair<UserWalletId, CardTypesResolver>, Basic.ToppedUp?> {
|
||||
|
||||
override fun convert(value: Pair<UserWalletId, CardTypesResolver>): Basic.ToppedUp? {
|
||||
val (userWalletId, resolver) = value
|
||||
val paramCardCurrency = ParamCardCurrencyConverter().convert(resolver) ?: return null
|
||||
|
||||
return Basic.ToppedUp(userWalletId, paramCardCurrency)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,14 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.tap.features.details.redux.SecurityOption
|
||||
|
||||
sealed class AnalyticsParam {
|
||||
|
||||
sealed class CurrencyType(val value: String) {
|
||||
class Currency(currency: com.tangem.tap.features.wallet.models.Currency) : CurrencyType(currency.currencySymbol)
|
||||
class Currency(currency: com.tangem.tap.domain.model.Currency) : CurrencyType(currency.currencySymbol)
|
||||
class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency)
|
||||
class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol)
|
||||
class FiatCurrency(fiatCurrency: AppCurrency) : CurrencyType(fiatCurrency.code)
|
||||
class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol)
|
||||
}
|
||||
|
||||
|
|
@ -23,15 +21,11 @@ sealed class AnalyticsParam {
|
|||
sealed class CardBalanceState(val value: String) {
|
||||
object Empty : CardBalanceState("Empty")
|
||||
object Full : CardBalanceState("Full")
|
||||
object CustomToken : CardBalanceState("Custom Token")
|
||||
object BlockchainError : CardBalanceState("Blockchain Error")
|
||||
object NoRate : CardBalanceState("No Rate")
|
||||
companion object
|
||||
}
|
||||
|
||||
sealed class RateApp(val value: String) {
|
||||
object Liked : RateApp("Liked")
|
||||
object Disliked : RateApp("Disliked")
|
||||
object Closed : RateApp("Close")
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +36,6 @@ sealed class AnalyticsParam {
|
|||
|
||||
sealed class UserCode(val value: String) {
|
||||
object AccessCode : UserCode("Access Code")
|
||||
object Passcode : UserCode("Passcode")
|
||||
}
|
||||
|
||||
sealed class SecurityMode(val value: String) {
|
||||
|
|
|
|||
|
|
@ -13,13 +13,6 @@ sealed class Basic(
|
|||
error: Throwable? = null,
|
||||
) : AnalyticsEvent("Basic", event, params, error) {
|
||||
|
||||
class BalanceLoaded(balance: AnalyticsParam.CardBalanceState) : Basic(
|
||||
event = "Balance Loaded",
|
||||
params = mapOf(
|
||||
AnalyticsParam.BALANCE to balance.value,
|
||||
),
|
||||
)
|
||||
|
||||
class CardWasScanned(
|
||||
source: AnalyticsParam.ScannedFrom,
|
||||
) : Basic(
|
||||
|
|
|
|||
|
|
@ -12,23 +12,14 @@ sealed class MainScreen(
|
|||
|
||||
class ScreenOpened : MainScreen("Screen opened")
|
||||
class ButtonScanCard : MainScreen("Button - Scan Card")
|
||||
class ButtonMyWallets : MainScreen("Button - My Wallets")
|
||||
|
||||
class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen(
|
||||
event = "Enable Biometric",
|
||||
params = mapOf("State" to state.value),
|
||||
)
|
||||
|
||||
class MainCurrencyChanged(currencyType: AnalyticsParam.CurrencyType) : MainScreen(
|
||||
event = "Main Currency Changed",
|
||||
params = mapOf("Currency Type" to currencyType.value),
|
||||
)
|
||||
|
||||
class NoticeRateAppButton(result: AnalyticsParam.RateApp) : MainScreen(
|
||||
event = "Notice - Rate The App Button Tapped",
|
||||
params = mapOf("Result" to result.value),
|
||||
)
|
||||
|
||||
class NoticeBackupYourWalletTapped : MainScreen("Notice - Backup Your Wallet Tapped")
|
||||
class NoticeScanYourCardTapped : MainScreen("Notice - Scan Your Card Tapped")
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
sealed class MyWallets(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("My Wallets", event, params) {
|
||||
|
||||
class MyWalletsScreenOpened : MyWallets(event = "My Wallets Screen Opened")
|
||||
|
||||
sealed class Button {
|
||||
class ScanNewCard : MyWallets(event = "Button - Scan New Card")
|
||||
class UnlockWithBiometrics : MyWallets(event = "Button - Unlock all with Face ID")
|
||||
class EditWalletTapped : MyWallets(event = "Button - Edit Wallet Tapped")
|
||||
class DeleteWalletTapped : MyWallets(event = "Button - Delete Wallet Tapped")
|
||||
class WalletUnlockTapped : MyWallets(event = "Button - Wallet Unlock Tapped")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
sealed class Portfolio(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Portfolio", event, params) {
|
||||
|
||||
class Refreshed : Portfolio("Refreshed")
|
||||
|
||||
class ButtonManageTokens : Portfolio("Button - Manage Tokens")
|
||||
|
||||
class TokenTapped : Portfolio("Token is Tapped")
|
||||
|
||||
class OrganizeTokens : Portfolio("Button - Organize Tokens")
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam.CurrencyType
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -13,50 +12,6 @@ sealed class Token(
|
|||
error: Throwable? = null,
|
||||
) : AnalyticsEvent(category, event, params, error) {
|
||||
|
||||
class Refreshed : Token("Token", "Refreshed")
|
||||
class ButtonExplore : Token("Token", "Button - Explore")
|
||||
|
||||
class ButtonRemoveToken(type: CurrencyType) : Token(
|
||||
"Token",
|
||||
"Button - Remove Token",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
class ButtonBuy(type: CurrencyType) : Token(
|
||||
category = "Token",
|
||||
event = "Button - Buy",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
class ButtonSell(type: CurrencyType) : Token(
|
||||
category = "Token",
|
||||
event = "Button - Sell",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
class ButtonExchange(type: CurrencyType) : Token(
|
||||
category = "Token",
|
||||
event = "Button - Exchange",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
class ButtonSend(type: CurrencyType) : Token(
|
||||
category = "Token",
|
||||
event = "Button - Send",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
class Bought(type: CurrencyType) : Token(
|
||||
category = "Token",
|
||||
event = "Token Bought",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
object ShowWalletAddress : Token(
|
||||
category = "Token",
|
||||
event = "Button - Show the Wallet Address",
|
||||
)
|
||||
|
||||
sealed class Receive(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package com.tangem.tap.common.analytics.handlers.amplitude
|
|||
import android.app.Application
|
||||
import com.amplitude.api.Amplitude
|
||||
import com.amplitude.api.AmplitudeClient
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.core.analytics.api.EventLogger
|
||||
import com.tangem.utils.converter.Converter
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.common.analytics.handlers.firebase
|
|||
|
||||
import android.os.Bundle
|
||||
import androidx.core.os.bundleOf
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.crashlytics.ktx.crashlytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
|
|
@ -29,8 +28,4 @@ internal class FirebaseClient : FirebaseAnalyticsClient {
|
|||
}
|
||||
|
||||
private fun Map<String, String>.toBundle(): Bundle = bundleOf(*this.toList().toTypedArray())
|
||||
|
||||
companion object {
|
||||
const val ORDER_EVENT = FirebaseAnalytics.Event.PURCHASE
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
fun TextAutoSize(
|
||||
text: String,
|
||||
fontSizeRange: FontSizeRange,
|
||||
modifier: Modifier = Modifier,
|
||||
textStyle: TextStyle = LocalTextStyle.current,
|
||||
) {
|
||||
val fontSizeValue = remember { mutableStateOf(fontSizeRange.max.value) }
|
||||
val readyToDraw = remember { mutableStateOf(false) }
|
||||
|
||||
val textState = remember { mutableStateOf(text) }
|
||||
if (textState.value != text) {
|
||||
readyToDraw.value = false
|
||||
fontSizeValue.value = fontSizeRange.max.value
|
||||
textState.value = text
|
||||
}
|
||||
|
||||
Text(
|
||||
modifier = modifier.drawWithContent { if (readyToDraw.value) drawContent() },
|
||||
text = text,
|
||||
softWrap = false,
|
||||
style = textStyle,
|
||||
fontSize = fontSizeValue.value.sp,
|
||||
onTextLayout = {
|
||||
if (it.hasVisualOverflow) {
|
||||
val nextFontSizeValue = fontSizeValue.value - fontSizeRange.step.value
|
||||
if (nextFontSizeValue <= fontSizeRange.min.value) {
|
||||
fontSizeValue.value = fontSizeRange.min.value
|
||||
readyToDraw.value = true
|
||||
} else {
|
||||
fontSizeValue.value = nextFontSizeValue * 0.8f
|
||||
}
|
||||
} else {
|
||||
readyToDraw.value = true
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
data class FontSizeRange(
|
||||
val min: TextUnit,
|
||||
val max: TextUnit,
|
||||
val step: TextUnit = DEFAULT_TEXT_STEP,
|
||||
) {
|
||||
init {
|
||||
require(min < max) { "min should be less than max, $this" }
|
||||
require(step.value > 0) { "step should be greater than 0, $this" }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DEFAULT_TEXT_STEP = 1.sp
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.material.ripple.LocalRippleTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
|
||||
/**
|
||||
* Used for disable ripple if button is enable = false
|
||||
*/
|
||||
@Composable
|
||||
fun ToggledRippleTheme(isEnabled: Boolean, content: @Composable () -> Unit) {
|
||||
val theme = LocalRippleTheme provides if (isEnabled) LocalRippleTheme.current else NoRippleTheme()
|
||||
CompositionLocalProvider(theme) { content() }
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
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(
|
||||
initialValue: T,
|
||||
onValueChange: (T) -> Unit,
|
||||
debounce: Long = 600,
|
||||
onEmitValueReceive: (T) -> Unit = {},
|
||||
): ValueDebouncer<T> {
|
||||
return remember {
|
||||
ValueDebouncer(
|
||||
initialValue = initialValue,
|
||||
debounceDuration = debounce,
|
||||
onEmitValueReceived = { emitValue ->
|
||||
emitValue?.let { onEmitValueReceive(it) }
|
||||
},
|
||||
onValueChanged = { changedValue ->
|
||||
changedValue?.let { onValueChange(it) }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T> valueDebouncerNullableAsState(
|
||||
initialValue: T?,
|
||||
onValueChange: (T?) -> Unit,
|
||||
debounce: Long = 400,
|
||||
onEmitValueReceive: (T?) -> Unit = {},
|
||||
): ValueDebouncer<T?> {
|
||||
return remember {
|
||||
ValueDebouncer(
|
||||
initialValue = initialValue,
|
||||
debounceDuration = debounce,
|
||||
onEmitValueReceived = onEmitValueReceive,
|
||||
onValueChanged = onValueChange,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
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.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
|
||||
private fun ErrorViewTest() {
|
||||
Box(Modifier.padding(16.dp)) {
|
||||
ErrorView(text = "Some error description")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.material.ripple.RippleAlpha
|
||||
import androidx.compose.material.ripple.RippleTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class NoRippleTheme : RippleTheme {
|
||||
@Composable
|
||||
override fun defaultColor() = Color.Unspecified
|
||||
|
||||
@Composable
|
||||
override fun rippleAlpha(): RippleAlpha = RippleAlpha(0.0f, 0.0f, 0.0f, 0.0f)
|
||||
}
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.text.isDigitsOnly
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun PinCodeWidget(
|
||||
config: PinViewConfig = tangemPinConfig,
|
||||
onPinChange: (String, Boolean) -> Unit = { pin, isLastSymbolEntered -> },
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
|
||||
val rTextFieldValue = remember { mutableStateOf(TextFieldValue("")) }
|
||||
val indexedSymbols: List<String?> = createPinSymbolsList(config.pinsCount, rTextFieldValue.value.text)
|
||||
|
||||
fun isLastSymbolEntered(): Boolean = rTextFieldValue.value.text.length == config.pinsCount
|
||||
|
||||
fun handleOnTextFieldValueChanged(value: TextFieldValue) {
|
||||
if (!value.text.isDigitsOnly()) return
|
||||
|
||||
if (value.text.length <= config.pinsCount) {
|
||||
rTextFieldValue.value = value
|
||||
onPinChange(value.text, isLastSymbolEntered())
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = config.modifier
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures {
|
||||
focusRequester.requestFocus()
|
||||
keyboardController?.show()
|
||||
}
|
||||
},
|
||||
) {
|
||||
Row {
|
||||
for (index in 0 until config.pinsCount) {
|
||||
PinElement(
|
||||
config = config,
|
||||
pinSymbol = indexedSymbols[index] ?: "",
|
||||
)
|
||||
}
|
||||
}
|
||||
TextField(
|
||||
modifier = Modifier
|
||||
.alpha(0f)
|
||||
.size(1.dp)
|
||||
.align(Alignment.Center)
|
||||
.focusRequester(focusRequester),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Decimal,
|
||||
imeAction = if (isLastSymbolEntered()) ImeAction.Done else ImeAction.Next,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { keyboardController?.hide() },
|
||||
),
|
||||
value = rTextFieldValue.value,
|
||||
onValueChange = ::handleOnTextFieldValueChanged,
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PinElement(config: PinViewConfig, pinSymbol: String) {
|
||||
Box(Modifier.padding(config.pinBoxPadding)) {
|
||||
Box(config.pinBoxModifier) {
|
||||
Text(
|
||||
text = pinSymbol,
|
||||
modifier = config.pinTextModifier.align(Alignment.Center),
|
||||
style = config.pinsTextStyle ?: LocalTextStyle.current,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPinSymbolsList(size: Int, text: String): List<String?> = List(size) {
|
||||
try {
|
||||
text[it].toString()
|
||||
} catch (ex: IndexOutOfBoundsException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
data class PinViewConfig(
|
||||
val modifier: Modifier = Modifier,
|
||||
val pinBoxModifier: Modifier = Modifier,
|
||||
val pinBoxPadding: Dp = 0.dp,
|
||||
val pinTextModifier: Modifier = Modifier,
|
||||
val pinsCount: Int = 4,
|
||||
val pinsTextStyle: TextStyle? = null,
|
||||
)
|
||||
|
||||
private val tangemPinConfig = PinViewConfig(
|
||||
modifier = Modifier
|
||||
.wrapContentSize(),
|
||||
pinBoxModifier = Modifier
|
||||
.width(42.dp)
|
||||
.height(56.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color(0xFFF0F0F0)),
|
||||
pinBoxPadding = 6.dp,
|
||||
pinTextModifier = Modifier,
|
||||
pinsCount = 4,
|
||||
pinsTextStyle = TextStyle(
|
||||
fontWeight = FontWeight(500),
|
||||
fontSize = 24.sp,
|
||||
),
|
||||
)
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PinCodeWidgetPreview() {
|
||||
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
|
||||
PinCodeWidget(tangemPinConfig)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.common.module.ModuleMessage
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun AddCustomTokenWarning(warning: ModuleMessage, converter: ModuleMessageConverter, modifier: Modifier = Modifier) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = colorResource(id = R.color.warning_warning),
|
||||
elevation = 4.dp,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.common_warning),
|
||||
color = colorResource(id = R.color.white),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
SpacerH8()
|
||||
Text(
|
||||
text = converter.convert(warning).message,
|
||||
color = colorResource(id = R.color.white),
|
||||
fontSize = 13.sp,
|
||||
lineHeight = 18.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
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]
|
||||
*/
|
||||
@Suppress("ComposableFunctionName")
|
||||
@Composable
|
||||
fun copyToClipboard(value: Any, label: String = "") {
|
||||
LocalContext.current.copyToClipboard(value, label)
|
||||
}
|
||||
|
||||
@Suppress("ComposableFunctionName")
|
||||
@Composable
|
||||
fun getFromClipboard(default: CharSequence? = null): CharSequence? {
|
||||
return LocalContext.current.getFromClipboard(default)
|
||||
}
|
||||
|
|
@ -14,6 +14,4 @@ fun Dp.toPx(): Float {
|
|||
return with(LocalDensity.current) { currentDp.toPx() }
|
||||
}
|
||||
|
||||
fun DpSize.halfWidth(): Dp = this.width / 2
|
||||
|
||||
fun DpSize.halfHeight(): Dp = this.height / 2
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import com.tangem.tap.common.extensions.hideKeyboard
|
||||
|
||||
@Composable
|
||||
fun LazyListState.OnBottomReached(loadMoreThreshold: Int, onLoadMore: () -> Unit) {
|
||||
require(loadMoreThreshold >= 0)
|
||||
val shouldLoadMore by remember {
|
||||
derivedStateOf {
|
||||
val lastVisibleItem = layoutInfo.visibleItemsInfo.lastOrNull()
|
||||
?: return@derivedStateOf false
|
||||
lastVisibleItem.index >= layoutInfo.totalItemsCount - 1 - loadMoreThreshold
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(shouldLoadMore) {
|
||||
if (shouldLoadMore) onLoadMore()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LazyListState.HideKeyboardOnScroll() {
|
||||
if (isScrollInProgress) {
|
||||
LocalView.current.hideKeyboard()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun <T> MutableState<List<T>>.addAndNotify(value: T) {
|
||||
this.value = this.value.toMutableList().apply { add(value) }
|
||||
}
|
||||
|
||||
fun <T> MutableState<List<T>>.removeAndNotify(value: T) {
|
||||
this.value = this.value.toMutableList().apply { remove(value) }
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import androidx.compose.ui.graphics.painter.Painter
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.sdk.extensions.dpToPx
|
||||
import com.tangem.sdk.extensions.pxToDp
|
||||
|
||||
/**
|
||||
|
|
@ -17,8 +16,5 @@ fun Painter.dpSize(): DpSize = DpSize(
|
|||
intrinsicSize.height.pxToDp().dp,
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun Float.dpToPx(): Float = LocalContext.current.dpToPx(this)
|
||||
|
||||
@Composable
|
||||
private fun Float.pxToDp(): Float = LocalContext.current.pxToDp(this)
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.content.res.AssetManager
|
||||
|
||||
fun AssetManager.readJsonFileToString(fileName: String): String =
|
||||
this.open("$fileName.json").bufferedReader().readText()
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
|
|
@ -9,9 +8,4 @@ fun Bitmap.toByteArray(): ByteArray {
|
|||
val stream = ByteArrayOutputStream()
|
||||
this.compress(Bitmap.CompressFormat.JPEG, 20, stream)
|
||||
return stream.toByteArray()
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun ByteArray.toBitmap(): Bitmap {
|
||||
return BitmapFactory.decodeByteArray(this, 0, this.size)
|
||||
}
|
||||
|
|
@ -7,10 +7,6 @@ import android.net.*
|
|||
import androidx.annotation.*
|
||||
import androidx.core.content.*
|
||||
|
||||
fun Context.isPermissionGranted(permission: String): Boolean {
|
||||
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
/**
|
||||
* Get uri to any resource type via given Resource Instance
|
||||
* @param resId - resource id
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import coil.request.ImageRequest
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun ImageRequest.Builder.cardImageData(any: Any?): ImageRequest.Builder = apply {
|
||||
data(any)
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.DrawableRes
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun ImageView.setDrawable(@DrawableRes resId: Int) {
|
||||
setImageDrawable(context.getDrawableCompat(resId))
|
||||
}
|
||||
|
|
@ -1,17 +1,11 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.text.Spanned
|
||||
import android.text.SpannedString
|
||||
import android.text.style.RelativeSizeSpan
|
||||
import androidx.core.text.buildSpannedString
|
||||
import com.tangem.common.extensions.isZero
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.text.NumberFormat
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
// TODO: move extensions to utils
|
||||
fun BigDecimal.toFormattedString(
|
||||
|
|
@ -29,105 +23,6 @@ fun BigDecimal.toFormattedString(
|
|||
return df.format(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* To formatted crypto currency string
|
||||
* Specific method because there is no crypto currency codes in Locale
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
fun BigDecimal.toFormattedCryptoCurrencyString(
|
||||
decimals: Int,
|
||||
currency: String,
|
||||
roundingMode: RoundingMode = RoundingMode.DOWN,
|
||||
limitNumberOfDecimals: Boolean = true,
|
||||
): String {
|
||||
val decimalsForRounding = if (limitNumberOfDecimals) {
|
||||
if (decimals > 8) 8 else decimals
|
||||
} else {
|
||||
decimals
|
||||
}
|
||||
try {
|
||||
val locale = Locale.getDefault()
|
||||
val formatter = NumberFormat.getCurrencyInstance(locale)
|
||||
// first create currency instance for "USD" with Locale default to replace currency to crypto later
|
||||
Currency.getInstance("USD")?.let { currencyTmp ->
|
||||
formatter.currency = currencyTmp
|
||||
formatter.maximumFractionDigits = decimalsForRounding
|
||||
formatter.minimumFractionDigits = 0
|
||||
formatter.isGroupingUsed = true
|
||||
formatter.roundingMode = roundingMode
|
||||
// cause formatter created for USD, replace Currency with Crypto symbol on right by Locale place
|
||||
return formatter.format(this).replace(currencyTmp.getSymbol(locale), "$currency ")
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Timber.e(e, "can't parse currency")
|
||||
}
|
||||
// if something went wrong - use old way to format
|
||||
val formattedAmount = this.toFormattedString(
|
||||
decimals = decimalsForRounding,
|
||||
roundingMode = roundingMode,
|
||||
locale = Locale.getDefault(),
|
||||
)
|
||||
return "$formattedAmount $currency "
|
||||
}
|
||||
|
||||
fun BigDecimal.toFiatRateString(fiatCurrencyName: String, fiatCode: String): String {
|
||||
try {
|
||||
val formatter = NumberFormat.getCurrencyInstance(Locale.getDefault())
|
||||
Currency.getInstance(fiatCode)?.let { currency ->
|
||||
formatter.currency = currency
|
||||
formatter.maximumFractionDigits = 2
|
||||
formatter.roundingMode = RoundingMode.HALF_UP
|
||||
return formatter.format(this).replace(currency.symbol, "$fiatCurrencyName ")
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Timber.e(e, "can't parse currency")
|
||||
}
|
||||
val value = this
|
||||
.setScale(2, RoundingMode.HALF_UP)
|
||||
.formatWithSpaces()
|
||||
return "$value $fiatCurrencyName"
|
||||
}
|
||||
|
||||
fun BigDecimal.toFiatString(
|
||||
rateValue: BigDecimal,
|
||||
fiatCurrencyName: String,
|
||||
fiatCode: String,
|
||||
formatWithSpaces: Boolean = false,
|
||||
): String {
|
||||
val fiatValue = rateValue.multiply(this)
|
||||
return fiatValue.toFormattedFiatValue(
|
||||
fiatCurrencyName = fiatCurrencyName,
|
||||
fiatCode = fiatCode,
|
||||
formatWithSpaces = formatWithSpaces,
|
||||
)
|
||||
}
|
||||
|
||||
fun BigDecimal.toFiatValue(rateValue: BigDecimal): BigDecimal {
|
||||
val fiatValue = rateValue.multiply(this)
|
||||
return fiatValue.setScale(2, RoundingMode.HALF_UP)
|
||||
}
|
||||
|
||||
fun BigDecimal.toFormattedFiatValue(
|
||||
fiatCurrencyName: String,
|
||||
fiatCode: String,
|
||||
formatWithSpaces: Boolean = false,
|
||||
): String {
|
||||
try {
|
||||
val formatter = NumberFormat.getCurrencyInstance(Locale.getDefault())
|
||||
Currency.getInstance(fiatCode)?.let { currency ->
|
||||
formatter.currency = currency
|
||||
formatter.maximumFractionDigits = 2
|
||||
formatter.roundingMode = RoundingMode.HALF_UP
|
||||
return formatter.format(this).replace(currency.symbol, "$fiatCurrencyName ")
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Timber.e(e, "can't parse currency")
|
||||
}
|
||||
val fiatValue = this.setScale(2, RoundingMode.HALF_UP)
|
||||
.let { if (formatWithSpaces) it.formatWithSpaces() else it }
|
||||
return " $fiatValue $fiatCurrencyName"
|
||||
}
|
||||
|
||||
fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString()
|
||||
|
||||
// 0.00 -> 0.00
|
||||
|
|
@ -146,70 +41,4 @@ fun BigDecimal.setPrecision(precision: Int, roundingMode: RoundingMode = Roundin
|
|||
return this.setScale(scale() - precision() + precision, roundingMode)
|
||||
}
|
||||
|
||||
fun BigDecimal.isPositive(): Boolean = this.compareTo(BigDecimal.ZERO) == 1
|
||||
fun BigDecimal.isNegative(): Boolean = this.compareTo(BigDecimal.ZERO) == -1
|
||||
fun BigDecimal.isGreaterThan(value: BigDecimal): Boolean = this.compareTo(value) == 1
|
||||
fun BigDecimal.isLessThan(value: BigDecimal): Boolean = this.compareTo(value) == -1
|
||||
|
||||
fun BigDecimal.isGreaterThanOrEqual(value: BigDecimal): Boolean {
|
||||
val compareResult = this.compareTo(value)
|
||||
return compareResult == 1 || compareResult == 0
|
||||
}
|
||||
|
||||
fun BigDecimal.isLessThanOrEqual(value: BigDecimal): Boolean {
|
||||
val compareResult = this.compareTo(value)
|
||||
return compareResult == -1 || compareResult == 0
|
||||
}
|
||||
|
||||
fun BigDecimal.formatAmountAsSpannedString(
|
||||
currencySymbol: String,
|
||||
reminderPartSizeProportion: Float = 0.7f,
|
||||
): SpannedString {
|
||||
val amount = this
|
||||
.setScale(2, RoundingMode.HALF_UP)
|
||||
.formatWithSpaces()
|
||||
val integer = amount.substringBefore('.')
|
||||
val reminder = amount.substringAfter('.')
|
||||
|
||||
// test formatter log Log.e("TEST ", BigDecimal("1234567890987654321.1234567890987654321").formatWithSpaces())
|
||||
|
||||
return buildSpannedString {
|
||||
append(integer)
|
||||
append('.')
|
||||
append(
|
||||
"$reminder $currencySymbol",
|
||||
RelativeSizeSpan(reminderPartSizeProportion),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun BigDecimal.formatWithSpaces(): String {
|
||||
val str = this.toString()
|
||||
var integerStr = str.substringBefore('.')
|
||||
val reminderStr = str.substringAfter('.')
|
||||
val packets = arrayListOf<String>()
|
||||
|
||||
var index: Int = integerStr.length
|
||||
while (0 < index) {
|
||||
if (index <= 3) {
|
||||
packets.add(integerStr)
|
||||
break
|
||||
}
|
||||
index -= 3
|
||||
packets.add(integerStr.substring(startIndex = index))
|
||||
integerStr = integerStr.substring(startIndex = 0, endIndex = index)
|
||||
}
|
||||
|
||||
return buildString {
|
||||
packets.reversed().forEachIndexed { index, packet ->
|
||||
append(packet)
|
||||
if (index != packets.lastIndex) append(' ')
|
||||
}
|
||||
if (reminderStr.isNotBlank()) {
|
||||
append('.')
|
||||
append(reminderStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
fun BigDecimal.isPositive(): Boolean = this.signum() == 1
|
||||
|
|
@ -46,10 +46,6 @@ suspend fun Store<AppState>.onUserWalletSelected(userWallet: UserWallet, sendAna
|
|||
state.globalState.tapWalletManager.onWalletSelected(userWallet, sendAnalyticsEvent)
|
||||
}
|
||||
|
||||
fun Store<*>.dispatchToastNotification(resId: Int) {
|
||||
dispatchOnMain(GlobalAction.ShowToastNotification(resId))
|
||||
}
|
||||
|
||||
fun Store<*>.dispatchErrorNotification(error: TapError) {
|
||||
dispatchOnMain(GlobalAction.ShowErrorNotification(error))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +1,12 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.text.Spannable
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.toSpannable
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.EncodeHintType
|
||||
import com.google.zxing.qrcode.QRCodeWriter
|
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
||||
import java.util.*
|
||||
|
||||
fun String?.ellipsizeBeforeSpace(allowedSize: Int): String {
|
||||
if (this.isNullOrBlank()) return ""
|
||||
val size = this.length
|
||||
val sizeDifference = size - allowedSize
|
||||
val endIndex = this.indexOf(" ")
|
||||
val startIndex = endIndex - sizeDifference
|
||||
val newString = this.removeRange(startIndex, endIndex)
|
||||
return newString.substring(0 until startIndex) + "..." +
|
||||
newString.substring(startIndex until newString.length)
|
||||
}
|
||||
|
||||
fun String.colorSegment(context: Context, color: Int, startIndex: Int = 0, endIndex: Int = this.length): Spannable {
|
||||
return this.toSpannable()
|
||||
.also { spannable ->
|
||||
spannable.setSpan(
|
||||
ForegroundColorSpan(ContextCompat.getColor(context, color)),
|
||||
startIndex,
|
||||
endIndex,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
||||
)
|
||||
}
|
||||
}
|
||||
import java.util.Hashtable
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun String.toQrCode(): Bitmap {
|
||||
|
|
@ -58,8 +29,6 @@ fun String.toQrCode(): Bitmap {
|
|||
return bmp
|
||||
}
|
||||
|
||||
fun String.urlEncode(): String = Uri.encode(this)
|
||||
|
||||
fun String.removePrefixOrNull(prefix: String): String? = when {
|
||||
startsWith(prefix) -> substring(prefix.length)
|
||||
else -> null
|
||||
|
|
|
|||
|
|
@ -1,24 +1,3 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun StringBuilder.appendIf(value: String, predicate: (String) -> Boolean): StringBuilder {
|
||||
if (predicate(value)) this.append(value)
|
||||
return this
|
||||
}
|
||||
|
||||
fun StringBuilder.appendIfNotNull(value: String?, prefix: String? = null, postfix: String? = null): StringBuilder {
|
||||
if (value == null) return this
|
||||
|
||||
prefix?.let { append(it) }
|
||||
append(value)
|
||||
postfix?.let { append(it) }
|
||||
return this
|
||||
}
|
||||
|
||||
fun String.appendIfNotNull(value: String?, prefix: String? = null, postfix: String? = null): String {
|
||||
return StringBuilder(this).apply { appendIfNotNull(value, prefix, postfix) }.toString()
|
||||
}
|
||||
|
||||
fun StringBuilder.breakLine(count: Int = 1): StringBuilder = append("\n".repeat(count))
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
fun Context.toast(message: String, length: Int = Toast.LENGTH_LONG) {
|
||||
Toast.makeText(this, message, length).show()
|
||||
}
|
||||
|
||||
fun Context.toast(@StringRes messageRes: Int, length: Int = Toast.LENGTH_LONG) {
|
||||
Toast.makeText(this, this.getString(messageRes), length).show()
|
||||
}
|
||||
|
||||
fun View.toast(message: String, length: Int = Toast.LENGTH_LONG) {
|
||||
context.toast(message, length)
|
||||
}
|
||||
|
||||
fun View.toast(@StringRes messageRes: Int, length: Int = Toast.LENGTH_LONG) {
|
||||
context.toast(context.getString(messageRes), length)
|
||||
}
|
||||
|
||||
fun Fragment.toast(message: String, length: Int = Toast.LENGTH_LONG) {
|
||||
context?.toast(message, length)
|
||||
}
|
||||
|
||||
fun Fragment.toast(@StringRes messageRes: Int, length: Int = Toast.LENGTH_LONG) {
|
||||
context?.let { it.toast(it.getString(messageRes), length) }
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.graphics.Color
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.graphics.luminance
|
||||
import androidx.core.graphics.toColorInt
|
||||
import com.tangem.blockchain.common.Token
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@ColorInt
|
||||
fun Token.getColor(isTestnet: Boolean = false): Int {
|
||||
val defaultColor = "#C7C7CC".toColorInt() // equivalent to R.color.lightGray4
|
||||
|
||||
return if (isTestnet) {
|
||||
defaultColor
|
||||
} else {
|
||||
try {
|
||||
("#" + this.contractAddress.subSequence(2..7).toString()).toColorInt()
|
||||
} catch (exception: Exception) {
|
||||
defaultColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@ColorInt
|
||||
fun Token.getTextColor(isTestnet: Boolean = false): Int = when {
|
||||
isTestnet -> Color.WHITE
|
||||
this.getColor().luminance > 0.5 -> Color.BLACK
|
||||
else -> Color.WHITE
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import androidx.transition.Transition
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
inline fun Transition.addListener(
|
||||
crossinline onStart: (animator: Transition) -> Unit = {},
|
||||
crossinline onEnd: (animator: Transition) -> Unit = {},
|
||||
crossinline onCancel: (animator: Transition) -> Unit = {},
|
||||
crossinline onPause: (animator: Transition) -> Unit = {},
|
||||
crossinline onRepeat: (animator: Transition) -> Unit = {},
|
||||
): Transition.TransitionListener {
|
||||
val listener = object : Transition.TransitionListener {
|
||||
override fun onTransitionStart(transition: Transition) = onStart(transition)
|
||||
override fun onTransitionEnd(transition: Transition) = onEnd(transition)
|
||||
override fun onTransitionCancel(transition: Transition) = onCancel(transition)
|
||||
override fun onTransitionPause(transition: Transition) = onPause(transition)
|
||||
override fun onTransitionResume(transition: Transition) = onRepeat(transition)
|
||||
}
|
||||
addListener(listener)
|
||||
return listener
|
||||
}
|
||||
|
|
@ -3,25 +3,12 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.content.*
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.annotation.*
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
|
||||
fun Context.getDrawableCompat(@DrawableRes drawableResId: Int): Drawable? {
|
||||
return ContextCompat.getDrawable(this, drawableResId)
|
||||
|
|
@ -72,48 +59,9 @@ fun View.hide(invokeBeforeStateChanged: (() -> Unit)? = null) {
|
|||
this.visibility = View.GONE
|
||||
}
|
||||
|
||||
fun View.invisible(invisible: Boolean = true, invokeBeforeStateChanged: (() -> Unit)? = null) {
|
||||
if (invisible) {
|
||||
if (this.visibility == View.INVISIBLE) return
|
||||
|
||||
invokeBeforeStateChanged?.invoke()
|
||||
this.visibility = View.INVISIBLE
|
||||
} else {
|
||||
this.show(invokeBeforeStateChanged)
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.dpToPixels(dp: Int): Int = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
dp.toFloat(),
|
||||
this.resources.displayMetrics,
|
||||
).toInt()
|
||||
|
||||
tailrec fun Context?.getActivity(): Activity? = this as? Activity
|
||||
?: (this as? ContextWrapper)?.baseContext?.getActivity()
|
||||
|
||||
fun MaterialCardView.setMargins(
|
||||
marginLeftDp: Int = 16,
|
||||
marginTopDp: Int = 8,
|
||||
marginRightDp: Int = 16,
|
||||
marginBottomDp: Int = 8,
|
||||
) {
|
||||
val params = this.layoutParams
|
||||
(params as ViewGroup.MarginLayoutParams).setMargins(
|
||||
context.dpToPixels(marginLeftDp),
|
||||
context.dpToPixels(marginTopDp),
|
||||
context.dpToPixels(marginRightDp),
|
||||
context.dpToPixels(marginBottomDp),
|
||||
)
|
||||
this.layoutParams = params
|
||||
}
|
||||
|
||||
fun View.hideKeyboard() {
|
||||
val inputMethodManager =
|
||||
context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
inputMethodManager?.hideSoftInputFromWindow(this.windowToken, 0)
|
||||
}
|
||||
|
||||
fun Context.copyToClipboard(value: Any, label: String = "") {
|
||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return
|
||||
|
||||
|
|
@ -140,37 +88,6 @@ fun Context.shareText(text: String) {
|
|||
startActivity(shareIntent)
|
||||
}
|
||||
|
||||
fun Fragment.shareText(text: String) {
|
||||
requireContext().shareText(text)
|
||||
}
|
||||
|
||||
fun View.getString(resId: Int, vararg formatArgs: Any?): String {
|
||||
return context.getString(resId, *formatArgs)
|
||||
}
|
||||
|
||||
fun View.animateVisibility(
|
||||
show: Boolean,
|
||||
durationMillis: Long = SHORT_ANIMATION_DURATION,
|
||||
hiddenVisibility: Int = View.GONE,
|
||||
) {
|
||||
if (show) {
|
||||
if (this.visibility == View.VISIBLE) return
|
||||
this.animate()
|
||||
.alpha(1f)
|
||||
.setDuration(durationMillis)
|
||||
.withStartAction {
|
||||
this.alpha = 0f
|
||||
this.isVisible = true
|
||||
}
|
||||
} else {
|
||||
if (this.visibility == hiddenVisibility) return
|
||||
this.animate()
|
||||
.alpha(0f)
|
||||
.setDuration(durationMillis)
|
||||
.withStartAction {
|
||||
this.visibility = hiddenVisibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val SHORT_ANIMATION_DURATION = 80L
|
||||
}
|
||||
|
|
@ -3,15 +3,9 @@ package com.tangem.tap.common.extensions
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewParent
|
||||
import androidx.core.view.forEach
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.Transition
|
||||
import androidx.transition.TransitionManager
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import com.tangem.tap.common.GlobalLayoutStateHandler
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -20,30 +14,6 @@ fun ViewGroup.inflate(viewToInflate: Int, attachToRoot: Boolean = false): View {
|
|||
return LayoutInflater.from(context).inflate(viewToInflate, this, attachToRoot)
|
||||
}
|
||||
|
||||
fun ViewParent?.beginDelayedTransition(transition: Transition = AutoTransition()) {
|
||||
if (this == null) Timber.e("Can't invoke beginDelayedTransition, because parent is NULL")
|
||||
(this as? ViewGroup)?.beginDelayedTransition(transition)
|
||||
}
|
||||
|
||||
fun ViewGroup.beginDelayedTransition(transition: Transition = AutoTransition()) {
|
||||
TransitionManager.beginDelayedTransition(this, transition)
|
||||
}
|
||||
|
||||
fun View.beginDelayedTransition(transition: Transition = AutoTransition()) {
|
||||
(this as? ViewGroup)?.beginDelayedTransition(transition)
|
||||
}
|
||||
|
||||
fun ChipGroup.fitChipsByGroupWidth() {
|
||||
val layoutStateHandler = GlobalLayoutStateHandler(this)
|
||||
layoutStateHandler.onStateChanged = stateHandler@{
|
||||
if (it.childCount < 2) {
|
||||
layoutStateHandler.detach()
|
||||
return@stateHandler
|
||||
}
|
||||
|
||||
val spacingBetweenViews = it.chipSpacingHorizontal * (it.childCount - 1)
|
||||
val width = (it.width - spacingBetweenViews) / it.childCount
|
||||
it.forEach { chip -> (chip as? Chip)?.width = width }
|
||||
layoutStateHandler.detach()
|
||||
}
|
||||
}
|
||||
|
|
@ -2,17 +2,6 @@ package com.tangem.tap.common.extensions
|
|||
|
||||
import android.webkit.WebView
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun WebView.configureSettings() {
|
||||
resumeTimers()
|
||||
settings.apply {
|
||||
javaScriptEnabled = true
|
||||
domStorageEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
fun WebView.stop() {
|
||||
stopLoading()
|
||||
pauseTimers()
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ class ScanQrCodeActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode != PERMISSION_REQUEST_CODE) return
|
||||
|
||||
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package com.tangem.tap.common.redux
|
|||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.core.navigation.StateDialog
|
||||
import com.tangem.tap.common.TestAction
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.domain.model.WalletAddressData
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ data class AppState(
|
|||
private val domainState: DomainState
|
||||
get() = domainStore.state
|
||||
|
||||
val domainNetworks: NetworkServices
|
||||
private val domainNetworks: NetworkServices
|
||||
get() = domainState.globalState.networkServices
|
||||
|
||||
val featureRepositoryProvider: FeatureRepositoryProvider
|
||||
|
|
|
|||
|
|
@ -20,22 +20,14 @@ class NotificationsHandler(coordinatorLayout: CoordinatorLayout) {
|
|||
private val basicCoordinatorLayout = WeakReference(coordinatorLayout)
|
||||
private var baseLayout = basicCoordinatorLayout
|
||||
|
||||
fun replaceBaseLayout(coordinatorLayout: CoordinatorLayout) {
|
||||
baseLayout = WeakReference(coordinatorLayout)
|
||||
}
|
||||
|
||||
fun returnBaseLayout() {
|
||||
baseLayout = basicCoordinatorLayout
|
||||
}
|
||||
|
||||
fun showNotification(message: String) {
|
||||
private fun showNotification(message: String) {
|
||||
baseLayout.get()?.let { layout ->
|
||||
Snackbar.make(layout, message, Snackbar.LENGTH_LONG)
|
||||
.also { snackbar -> snackbar.show() }
|
||||
}
|
||||
}
|
||||
|
||||
fun showDebugNotification(message: String) {
|
||||
private fun showDebugNotification(message: String) {
|
||||
baseLayout.get()?.let { layout ->
|
||||
Snackbar.make(layout, message, Snackbar.LENGTH_LONG)
|
||||
.also { snackbar ->
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
package com.tangem.tap.common.redux
|
||||
|
||||
import org.rekotlin.Action
|
||||
|
||||
abstract class Request : Action {
|
||||
abstract suspend fun execute()
|
||||
}
|
||||
|
|
@ -14,7 +14,6 @@ import com.tangem.tap.common.feedback.FeedbackManager
|
|||
import com.tangem.tap.common.redux.DebugErrorAction
|
||||
import com.tangem.tap.common.redux.ErrorAction
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.common.redux.ToastNotificationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
|
|
@ -25,7 +24,6 @@ sealed class GlobalAction : Action {
|
|||
|
||||
// notifications
|
||||
data class ShowNotification(override val messageResource: Int) : GlobalAction(), NotificationAction
|
||||
data class ShowToastNotification(override val messageResource: Int) : GlobalAction(), ToastNotificationAction
|
||||
data class ShowErrorNotification(override val error: TapError) : GlobalAction(), ErrorAction
|
||||
data class DebugShowErrorNotification(override val error: TapError) : GlobalAction(), DebugErrorAction
|
||||
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
|
|||
return Result.success(products)
|
||||
}
|
||||
|
||||
suspend fun applyPromoCode(promoCode: String, productType: ProductType): Result<TangemProduct> {
|
||||
private suspend fun applyPromoCode(promoCode: String, productType: ProductType): Result<TangemProduct> {
|
||||
val checkout = checkouts[productType] ?: return Result.failure(Exception("No checkout"))
|
||||
|
||||
val result = if (promoCode.isBlank()) {
|
||||
|
|
|
|||
|
|
@ -19,26 +19,7 @@ import kotlin.coroutines.suspendCoroutine
|
|||
|
||||
@Suppress("LargeClass")
|
||||
class ShopifyService(private val application: Application, val shop: ShopifyShop) {
|
||||
val client: GraphClient by lazy { initClient() }
|
||||
|
||||
suspend fun getShopName(): Result<String> {
|
||||
val query = query { rootQuery: QueryRootQuery ->
|
||||
rootQuery
|
||||
.shop { shopQuery: ShopQuery ->
|
||||
shopQuery
|
||||
.name()
|
||||
}
|
||||
}
|
||||
return when (val result = queryAsync(query)) {
|
||||
is GraphCallResult.Success -> {
|
||||
val name = result.response.data!!.shop.name
|
||||
Result.success(name)
|
||||
}
|
||||
is GraphCallResult.Failure -> {
|
||||
Result.failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
private val client: GraphClient by lazy { initClient() }
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
suspend fun getProducts(collectionTitleFilter: String? = null): Result<List<Product>> {
|
||||
|
|
|
|||
|
|
@ -28,12 +28,12 @@ abstract class BaseTruncate : Truncate {
|
|||
protected var hasBeenTruncated = false
|
||||
|
||||
override fun apply(tv: TextView, text: String, with: String): String {
|
||||
val roughLength = getRoughFitLength(tv, text, with)
|
||||
val roughLength = getRoughFitLength(tv, text)
|
||||
val fittedText = preciseFitting(tv, roughTruncate(text, roughLength), with)
|
||||
return if (hasBeenTruncated) attachWith(fittedText, with) else fittedText
|
||||
}
|
||||
|
||||
protected fun getRoughFitLength(tv: TextView, text: String, with: String): Int {
|
||||
private fun getRoughFitLength(tv: TextView, text: String): Int {
|
||||
val existingSpace = tv.measuredWidth - (tv.paddingStart + tv.paddingEnd)
|
||||
val textWillTakeSpace = tv.paint.measureText(text)
|
||||
val overSizeRatio: Float = textWillTakeSpace / existingSpace
|
||||
|
|
@ -44,7 +44,7 @@ abstract class BaseTruncate : Truncate {
|
|||
return maxLengthOfText.toInt()
|
||||
}
|
||||
|
||||
protected fun preciseFitting(tv: TextView, text: String, with: String): String {
|
||||
private fun preciseFitting(tv: TextView, text: String, with: String): String {
|
||||
if (!hasBeenTruncated) return text
|
||||
|
||||
val spaceForText = tv.measuredWidth - (tv.paddingStart + tv.paddingEnd)
|
||||
|
|
@ -126,11 +126,5 @@ fun TextView.truncateWith(text: String, type: TruncateType, with: String = "..."
|
|||
return truncate.apply(this, text, with)
|
||||
}
|
||||
|
||||
fun TextView.truncateStartWith(text: String, with: String = "..."): String =
|
||||
this.truncateWith(text, TruncateType.START, with)
|
||||
|
||||
fun TextView.truncateMiddleWith(text: String, with: String = "..."): String =
|
||||
this.truncateWith(text, TruncateType.MIDDLE, with)
|
||||
|
||||
fun TextView.truncateEndWith(text: String, with: String = "..."): String =
|
||||
this.truncateWith(text, TruncateType.END, with)
|
||||
this.truncateWith(text, TruncateType.MIDDLE, with)
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package com.tangem.tap.common.utils
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
private val mainLooper by lazy { Looper.getMainLooper() }
|
||||
private val mainHandler by lazy { Handler(mainLooper) }
|
||||
|
||||
/**
|
||||
* A subscriber interface for safely subscribing to state changes in a Store.
|
||||
*
|
||||
* @param State the type of the state in the Store
|
||||
*/
|
||||
interface SafeStoreSubscriber<State> : StoreSubscriber<State> {
|
||||
|
||||
/**
|
||||
* A function that will be called when the state in the Store changes.
|
||||
*
|
||||
* @param state the new state in the Store
|
||||
*/
|
||||
override fun newState(state: State) {
|
||||
if (Thread.currentThread() != mainLooper.thread) {
|
||||
mainHandler.post { newStateOnMain(state) }
|
||||
} else {
|
||||
newStateOnMain(state)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that will be called on the main thread when the state in the Store changes.
|
||||
*
|
||||
* @param state the new state in the Store
|
||||
*/
|
||||
fun newStateOnMain(state: State)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue