Updated on 2026-08-14
This commit is contained in:
parent
330ae73357
commit
e7efe7b76a
4636 changed files with 234864 additions and 63507 deletions
|
|
@ -1,20 +1,21 @@
|
|||
plugins {
|
||||
id("java-library")
|
||||
id("org.jetbrains.kotlin.jvm")
|
||||
kotlin("kapt")
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** DI */
|
||||
implementation(Library.hiltCore)
|
||||
kapt(Library.hiltKapt)
|
||||
// region DI
|
||||
implementation(deps.hilt.core)
|
||||
kapt(deps.hilt.kapt)
|
||||
// endregion
|
||||
|
||||
/** Coroutines */
|
||||
implementation(Library.coroutine)
|
||||
}
|
||||
// region Coroutines
|
||||
implementation(deps.kotlin.coroutines)
|
||||
// endregion
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
// region Time dependencies
|
||||
implementation(deps.jodatime)
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.tangem.utils" />
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.utils
|
||||
|
||||
/**
|
||||
* Convert address to brief format. Example, 33BddS...ga2B.
|
||||
* If [this.length] is less than a sum of [startCharsCount] and [endCharsCount], return [this].
|
||||
*/
|
||||
fun String.toBriefAddressFormat(startCharsCount: Int = 6, endCharsCount: Int = 4): String {
|
||||
return if (startCharsCount + endCharsCount < length) {
|
||||
substring(startIndex = 0, endIndex = startCharsCount) + "..." +
|
||||
substring(startIndex = length - endCharsCount, endIndex = length)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,15 @@
|
|||
package com.tangem.utils
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
inline fun <reified T : Enum<T>> safeValueOf(type: String, default: T): T {
|
||||
return try {
|
||||
java.lang.Enum.valueOf(T::class.java, type)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
default
|
||||
}
|
||||
}
|
||||
|
||||
fun BigDecimal?.isNullOrZero(): Boolean {
|
||||
return this == null || this.compareTo(BigDecimal.ZERO) == 0
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
package com.tangem.utils
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.util.*
|
||||
|
||||
// todo determine where to place this extensions
|
||||
fun BigDecimal.toFormattedString(
|
||||
decimals: Int,
|
||||
roundingMode: RoundingMode = RoundingMode.DOWN,
|
||||
locale: Locale = Locale.US,
|
||||
): String {
|
||||
val symbols = DecimalFormatSymbols(locale)
|
||||
val df = DecimalFormat().apply {
|
||||
decimalFormatSymbols = symbols
|
||||
maximumFractionDigits = decimals
|
||||
minimumFractionDigits = 0
|
||||
isGroupingUsed = false
|
||||
this.roundingMode = roundingMode
|
||||
}
|
||||
return df.format(this)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun BigDecimal.toFormattedCurrencyString(
|
||||
decimals: Int,
|
||||
currency: String,
|
||||
roundingMode: RoundingMode = RoundingMode.DOWN,
|
||||
limitNumberOfDecimals: Boolean = true,
|
||||
): String {
|
||||
val decimalsForRounding = if (limitNumberOfDecimals) {
|
||||
if (decimals > 8) 8 else decimals
|
||||
} else {
|
||||
decimals
|
||||
}
|
||||
val formattedAmount = this.toFormattedString(
|
||||
decimals = decimalsForRounding,
|
||||
roundingMode = roundingMode,
|
||||
)
|
||||
return "$formattedAmount $currency"
|
||||
}
|
||||
|
||||
fun BigDecimal.toFiatString(
|
||||
rateValue: BigDecimal,
|
||||
fiatCurrencyName: String,
|
||||
formatWithSpaces: Boolean = false,
|
||||
): String {
|
||||
val fiatValue = rateValue.multiply(this)
|
||||
return fiatValue.toFormattedFiatValue(fiatCurrencyName, formatWithSpaces)
|
||||
}
|
||||
|
||||
fun BigDecimal.toFormattedFiatValue(
|
||||
fiatCurrencyName: String,
|
||||
formatWithSpaces: Boolean = false,
|
||||
): String {
|
||||
val fiatValue = this.setScale(2, RoundingMode.HALF_UP)
|
||||
.let { if (formatWithSpaces) it.formatWithSpaces() else it }
|
||||
return " $fiatValue $fiatCurrencyName"
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
||||
10
core/utils/src/main/java/com/tangem/utils/Provider.kt
Normal file
10
core/utils/src/main/java/com/tangem/utils/Provider.kt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.utils
|
||||
|
||||
/**
|
||||
* Provider for lazy initialization
|
||||
*
|
||||
* @param action initialization action
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class Provider<T>(action: () -> T) : () -> T by action
|
||||
10
core/utils/src/main/java/com/tangem/utils/ProviderSuspend.kt
Normal file
10
core/utils/src/main/java/com/tangem/utils/ProviderSuspend.kt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.utils
|
||||
|
||||
/**
|
||||
* Provider for suspend lazy initialization
|
||||
*
|
||||
* @param action initialization action
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ProviderSuspend<T>(action: suspend () -> T) : suspend () -> T by action
|
||||
15
core/utils/src/main/java/com/tangem/utils/StringsSigns.kt
Normal file
15
core/utils/src/main/java/com/tangem/utils/StringsSigns.kt
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.utils
|
||||
|
||||
object StringsSigns {
|
||||
|
||||
const val DOT = "•"
|
||||
const val PLUS = "+"
|
||||
const val MINUS = "-"
|
||||
const val DASH_SIGN = "—"
|
||||
const val LOWER_SIGN = "<"
|
||||
const val TILDE_SIGN = "~"
|
||||
const val INFINITY_SIGN = "∞"
|
||||
const val NON_BREAKING_SPACE = '\u00A0'
|
||||
const val PERCENT = "%"
|
||||
const val THREE_STARS = "\u2217\u2217\u2217"
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.utils
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
object SupportedLanguages {
|
||||
const val ENGLISH = "en"
|
||||
const val RUSSIAN = "ru"
|
||||
const val GERMAN = "de"
|
||||
const val FRANCH = "fr"
|
||||
const val ITALIAN = "it"
|
||||
const val JAPANESE = "ja"
|
||||
const val UKRAINIAN = "uk"
|
||||
const val CHINESE = "uk"
|
||||
const val SPANISH = "es"
|
||||
|
||||
val supportedLangugeCodes = listOf(
|
||||
ENGLISH,
|
||||
RUSSIAN,
|
||||
GERMAN,
|
||||
FRANCH,
|
||||
ITALIAN,
|
||||
JAPANESE,
|
||||
UKRAINIAN,
|
||||
CHINESE,
|
||||
SPANISH,
|
||||
)
|
||||
|
||||
fun getCurrentSupportedLanguageCode(): String {
|
||||
val locale = Locale.getDefault()
|
||||
|
||||
return if (supportedLangugeCodes.contains(locale.language)) {
|
||||
locale.language
|
||||
} else {
|
||||
ENGLISH
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package com.tangem.utils
|
||||
|
||||
const val H24_MILLIS = 24L * 60 * 60 * 1000
|
||||
const val WEEK_MILLIS = 7L * H24_MILLIS
|
||||
|
|
@ -1,8 +1,29 @@
|
|||
package com.tangem.utils.converter
|
||||
|
||||
interface Converter<I, O> {
|
||||
interface Converter<I : Any, O : Any?> {
|
||||
|
||||
fun convert(value: I): O
|
||||
fun convertList(input: List<I>): List<O> {
|
||||
return input.map { convert(it) }
|
||||
|
||||
fun convertList(input: Collection<I>): List<O> {
|
||||
return input.map(::convert)
|
||||
}
|
||||
|
||||
fun convertSet(input: Collection<I>): Set<O> {
|
||||
return input.mapTo(hashSetOf(), ::convert)
|
||||
}
|
||||
|
||||
fun convertListIgnoreErrors(input: Collection<I>, onError: ((Throwable) -> Unit)? = null): List<O> {
|
||||
return input.mapNotNull {
|
||||
try {
|
||||
convert(it)
|
||||
} catch (throwable: Throwable) {
|
||||
onError?.invoke(throwable)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> T?.asMandatory(name: String): T {
|
||||
return this ?: error("$name must not be null")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.utils.converter
|
||||
|
||||
interface TwoWayConverter<I, O> : Converter<I, O> {
|
||||
interface TwoWayConverter<I : Any, O> : Converter<I, O> {
|
||||
|
||||
fun convertBack(value: O): I
|
||||
fun convertListBack(input: List<O>): List<I> {
|
||||
|
||||
fun convertListBack(input: Collection<O>): List<I> {
|
||||
return input.map { convertBack(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,19 +2,31 @@ package com.tangem.utils.coroutines
|
|||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import java.util.concurrent.Executors
|
||||
import javax.inject.Inject
|
||||
|
||||
interface CoroutineDispatcherProvider {
|
||||
val main: CoroutineDispatcher
|
||||
val mainImmediate: CoroutineDispatcher
|
||||
val io: CoroutineDispatcher
|
||||
val default: CoroutineDispatcher
|
||||
val single: CoroutineDispatcher
|
||||
}
|
||||
|
||||
class AppCoroutineDispatcherProvider @Inject constructor() : CoroutineDispatcherProvider {
|
||||
override val main: CoroutineDispatcher = Dispatchers.Main
|
||||
override val mainImmediate: CoroutineDispatcher = Dispatchers.Main.immediate
|
||||
override val io: CoroutineDispatcher = Dispatchers.IO
|
||||
override val default: CoroutineDispatcher = Dispatchers.Default
|
||||
override val single: CoroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher()
|
||||
}
|
||||
|
||||
class TestingCoroutineDispatcherProvider(
|
||||
override val main: CoroutineDispatcher = Dispatchers.Unconfined,
|
||||
override val mainImmediate: CoroutineDispatcher = Dispatchers.Unconfined,
|
||||
override val io: CoroutineDispatcher = Dispatchers.Unconfined,
|
||||
) : CoroutineDispatcherProvider
|
||||
override val default: CoroutineDispatcher = Dispatchers.Unconfined,
|
||||
override val single: CoroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher(),
|
||||
) : CoroutineDispatcherProvider
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
package com.tangem.utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
suspend fun <R> runCatching(dispatcher: CoroutineDispatcher, block: suspend () -> R): Result<R> {
|
||||
return runCatching {
|
||||
|
|
@ -13,19 +10,36 @@ suspend fun <R> runCatching(dispatcher: CoroutineDispatcher, block: suspend () -
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun <R> waitForDelay(delay: Long, block: suspend CoroutineScope.() -> R): R = coroutineScope {
|
||||
val minWaitingTime = async { delay(delay) }
|
||||
val actionInvoke = async { block() }
|
||||
minWaitingTime.await()
|
||||
actionInvoke.await()
|
||||
}
|
||||
|
||||
class Debouncer {
|
||||
|
||||
private var debounceJob: Job? = null
|
||||
|
||||
fun debounce(
|
||||
waitMs: Long = 300L,
|
||||
coroutineScope: CoroutineScope,
|
||||
destinationFunction: () -> Unit,
|
||||
waitMs: Long = 300L,
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
forceUpdate: Boolean = false,
|
||||
destinationFunction: suspend () -> Unit,
|
||||
) {
|
||||
debounceJob?.cancel()
|
||||
debounceJob = coroutineScope.launch {
|
||||
delay(waitMs)
|
||||
debounceJob = coroutineScope.launch(context) {
|
||||
if (!forceUpdate) delay(waitMs)
|
||||
destinationFunction.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
fun release() {
|
||||
debounceJob?.cancel()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_WAIT_TIME_MS = 500L
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.utils.coroutines
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class DelayedWork
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Job holder. It is automatically finished old job if new one is started
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class JobHolder {
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
/** Update current [JobHolder.job] and return new [job] */
|
||||
fun update(job: Job): Job {
|
||||
this.job?.cancel()
|
||||
this.job = job
|
||||
return job
|
||||
}
|
||||
|
||||
/** Cancel current [job] */
|
||||
fun cancel() {
|
||||
job?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
fun Job.saveIn(jobHolder: JobHolder): Job = jobHolder.update(job = this)
|
||||
|
||||
suspend fun Job.saveInAndJoin(jobHolder: JobHolder) = saveIn(jobHolder).join()
|
||||
|
||||
fun CoroutineScope.withDebounce(jobHolder: JobHolder, timeMillis: Long = 800L, function: () -> Unit) {
|
||||
launch {
|
||||
delay(timeMillis = timeMillis)
|
||||
|
||||
function()
|
||||
}
|
||||
.saveIn(jobHolder)
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.tangem.utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class PeriodicTask<T>(
|
||||
private val delay: Long,
|
||||
private val task: suspend () -> Result<T>,
|
||||
private val onSuccess: (T) -> Unit,
|
||||
private val onError: (Throwable) -> Unit,
|
||||
private val isDelayFirst: Boolean = false,
|
||||
) {
|
||||
|
||||
private var isActive: AtomicBoolean = AtomicBoolean(false)
|
||||
|
||||
suspend fun runTaskWithDelay() {
|
||||
isActive.set(true)
|
||||
if (isDelayFirst) {
|
||||
delay(delay)
|
||||
}
|
||||
while (isActive.get()) {
|
||||
task.invoke()
|
||||
.onSuccess {
|
||||
if (!isActive.get()) {
|
||||
return@onSuccess
|
||||
}
|
||||
onSuccess.invoke(it)
|
||||
}
|
||||
.onFailure {
|
||||
if (!isActive.get()) {
|
||||
return@onFailure
|
||||
}
|
||||
onError.invoke(it)
|
||||
}
|
||||
delay(delay)
|
||||
}
|
||||
}
|
||||
|
||||
fun cancel() {
|
||||
isActive.set(false)
|
||||
}
|
||||
}
|
||||
|
||||
class SingleTaskScheduler<T> {
|
||||
|
||||
private var lastTask: PeriodicTask<T>? = null
|
||||
|
||||
fun scheduleTask(scope: CoroutineScope, task: PeriodicTask<T>) {
|
||||
lastTask?.cancel()
|
||||
lastTask = task
|
||||
scope.launch {
|
||||
task.runTaskWithDelay()
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelTask() {
|
||||
lastTask?.cancel()
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ package com.tangem.utils.detekt
|
|||
AnnotationTarget.TYPE,
|
||||
AnnotationTarget.EXPRESSION,
|
||||
AnnotationTarget.FILE,
|
||||
AnnotationTarget.TYPEALIAS
|
||||
AnnotationTarget.TYPEALIAS,
|
||||
)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class UnusedRequiredComponent(val description: String)
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.utils.di
|
||||
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.DelayedWork
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DelayedWorkCoroutineModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@DelayedWork
|
||||
fun provideDelayedWorkCoroutineScope(coroutineDispatcherProvider: CoroutineDispatcherProvider): CoroutineScope {
|
||||
return CoroutineScope(SupervisorJob() + coroutineDispatcherProvider.io)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.utils.extensions
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
/** Returns [BigDecimal] or [BigDecimal.ZERO] if [this] is null */
|
||||
fun BigDecimal?.orZero(): BigDecimal = this ?: BigDecimal.ZERO
|
||||
|
||||
/** Checks if [this] is [BigDecimal] */
|
||||
fun BigDecimal.isZero(): Boolean = this.compareTo(BigDecimal.ZERO) == 0
|
||||
|
||||
/** Checks if [this] is positive */
|
||||
fun BigDecimal.isPositive(): Boolean = this.signum() == 1
|
||||
|
||||
/** Removes trailing zeros and returns plain [String] */
|
||||
fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString()
|
||||
|
||||
/** Compares two [BigDecimal] numbers */
|
||||
infix fun BigDecimal.isEqualTo(other: BigDecimal): Boolean = this.compareTo(other) == 0
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.utils.extensions
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun Char.isNotWhitespace(): Boolean = !this.isWhitespace()
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.utils.extensions
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if the [Collection] contains a single item.
|
||||
*
|
||||
* @return [Boolean] indicating whether the [Collection] contains exactly one element.
|
||||
*/
|
||||
fun <T> Collection<T>.isSingleItem(): Boolean = this.size == 1
|
||||
|
||||
/**
|
||||
* Creates a shallow copy of this [Collection].
|
||||
*
|
||||
* @return A copy of the [Collection].
|
||||
*/
|
||||
fun <T> Collection<T>.copy(): Collection<T> {
|
||||
return this.map { it }
|
||||
}
|
||||
|
||||
inline fun <T> List<T>.indexOfFirstOrNull(predicate: (T) -> Boolean): Int? {
|
||||
val index = indexOfFirst(predicate)
|
||||
return if (index == -1) null else index
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.utils.extensions
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.LocalDate
|
||||
|
||||
fun DateTime.isToday(): Boolean = LocalDate.now().equals(LocalDate(this))
|
||||
|
||||
fun DateTime.isYesterday(): Boolean = LocalDate.now().minusDays(1).equals(LocalDate(this))
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.utils.extensions
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun Int.isEven(): Boolean = this % 2 == 0
|
||||
75
core/utils/src/main/java/com/tangem/utils/extensions/List.kt
Normal file
75
core/utils/src/main/java/com/tangem/utils/extensions/List.kt
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package com.tangem.utils.extensions
|
||||
|
||||
/**
|
||||
* Removes elements from the collection based on the provided predicate.
|
||||
*
|
||||
* !!!This function is not thread-safe!!!
|
||||
*
|
||||
* @param predicate The condition to remove an element.
|
||||
* @return [Boolean] indicating whether an element was removed.
|
||||
*/
|
||||
fun <T> MutableList<T>.removeBy(predicate: (T) -> Boolean): Boolean {
|
||||
val toRemove = this.filter(predicate)
|
||||
this.removeAll(toRemove)
|
||||
return toRemove.isNotEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an element in the list with the provided item based on the predicate.
|
||||
*
|
||||
* @param item The element to replace the existing one.
|
||||
* @param predicate The condition to replace an existing element.
|
||||
* @return [Boolean] indicating whether an element was replaced.
|
||||
*/
|
||||
inline fun <T> MutableList<T>.replaceBy(item: T, predicate: (T) -> Boolean): Boolean {
|
||||
val index = indexOfFirst(predicate)
|
||||
|
||||
if (index == -1) {
|
||||
return false
|
||||
}
|
||||
|
||||
this[index] = item
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified element to the list or replaces an existing element.
|
||||
*
|
||||
* !!!This function is not thread-safe!!!
|
||||
*
|
||||
* @param item The element to be added or replace the existing one.
|
||||
* @param predicate The condition to replace an existing element.
|
||||
* @return The modified [List] after adding or replacing the element.
|
||||
*/
|
||||
inline fun <T> List<T>.addOrReplace(item: T, predicate: (T) -> Boolean): List<T> {
|
||||
val mutableList = this.toMutableList()
|
||||
|
||||
mutableList.addOrReplace(item, predicate)
|
||||
|
||||
return mutableList
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified element to the mutable list or replaces an existing element.
|
||||
*
|
||||
* !!!This function is not thread-safe!!!
|
||||
*
|
||||
* @param item The element to be added or replace the existing one.
|
||||
* @param predicate The condition to replace an existing element.
|
||||
*/
|
||||
inline fun <T> MutableList<T>.addOrReplace(item: T, predicate: (T) -> Boolean) {
|
||||
val isReplaced = replaceBy(item, predicate)
|
||||
|
||||
if (!isReplaced) {
|
||||
add(item)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> List<T>.filterIf(condition: Boolean, predicate: (T) -> Boolean): List<T> {
|
||||
return if (condition) {
|
||||
this.filter(predicate)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
51
core/utils/src/main/java/com/tangem/utils/extensions/Set.kt
Normal file
51
core/utils/src/main/java/com/tangem/utils/extensions/Set.kt
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.utils.extensions
|
||||
|
||||
/**
|
||||
* Replaces an element in the set with the provided item based on the predicate.
|
||||
*
|
||||
* !!!This function is not thread-safe!!!
|
||||
*
|
||||
* @param item The element to replace the existing one.
|
||||
* @param predicate The condition to replace an existing element.
|
||||
* @return [Boolean] indicating whether an element was replaced.
|
||||
*/
|
||||
inline fun <T> MutableSet<T>.replaceBy(item: T, predicate: (T) -> Boolean): Boolean {
|
||||
val foundItem = firstOrNull(predicate) ?: return false
|
||||
|
||||
remove(foundItem)
|
||||
add(item)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified element to the set or replaces an existing element.
|
||||
*
|
||||
* !!!This function is not thread-safe!!!
|
||||
*
|
||||
* @param item The element to be added or replace the existing one.
|
||||
* @param predicate The condition to replace an existing element.
|
||||
* @return The modified [Set] after adding or replacing the element.
|
||||
*/
|
||||
inline fun <T> Set<T>.addOrReplace(item: T, predicate: (T) -> Boolean): Set<T> {
|
||||
val mutableList = this.toMutableSet()
|
||||
val isReplaced = mutableList.replaceBy(item, predicate)
|
||||
|
||||
if (!isReplaced) {
|
||||
mutableList.add(item)
|
||||
}
|
||||
|
||||
return mutableList
|
||||
}
|
||||
|
||||
inline fun <T> Set<T>.addOrReplace(items: Set<T>, predicate: (T, T) -> Boolean): Set<T> {
|
||||
val updatedValues = this.toMutableSet()
|
||||
|
||||
items.forEach { newValue ->
|
||||
val isReplaced = updatedValues.replaceBy(item = newValue, predicate = { predicate(it, newValue) })
|
||||
|
||||
if (!isReplaced) updatedValues.add(newValue)
|
||||
}
|
||||
|
||||
return updatedValues
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.utils.extensions
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
const val DELAY_SDK_DIALOG_CLOSE = 1400L
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.utils.transformer
|
||||
|
||||
/**
|
||||
* Transforms state to updated state.
|
||||
*/
|
||||
interface Transformer<S> {
|
||||
fun transform(prevState: S): S
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.utils.version
|
||||
|
||||
interface AppVersionProvider {
|
||||
|
||||
val versionName: String
|
||||
|
||||
val versionCode: Int
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue