Updated on 2026-08-14
This commit is contained in:
commit
2da48c037a
961 changed files with 24212 additions and 13399 deletions
|
|
@ -0,0 +1,138 @@
|
|||
package com.tangem.core.analytics.models
|
||||
|
||||
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")
|
||||
companion object
|
||||
}
|
||||
|
||||
sealed class RateApp(val value: String) {
|
||||
object Liked : RateApp("Liked")
|
||||
object Disliked : RateApp("Disliked")
|
||||
object Closed : RateApp("Close")
|
||||
}
|
||||
|
||||
sealed class OnOffState(val value: String) {
|
||||
object On : OnOffState("On")
|
||||
object Off : OnOffState("Off")
|
||||
}
|
||||
|
||||
sealed class OrganizeSortType(val value: String) {
|
||||
object ByBalance : OrganizeSortType("By Balance")
|
||||
object Manually : OrganizeSortType("Manually")
|
||||
}
|
||||
|
||||
sealed class UserCode(val value: String) {
|
||||
object AccessCode : UserCode("Access Code")
|
||||
object Passcode : UserCode("Passcode")
|
||||
}
|
||||
|
||||
sealed class AccessCodeRecoveryStatus(val value: String) {
|
||||
|
||||
val key: String = "Status"
|
||||
|
||||
object Enabled : AccessCodeRecoveryStatus("Enabled")
|
||||
object Disabled : AccessCodeRecoveryStatus("Disabled")
|
||||
|
||||
companion object {
|
||||
fun from(enabled: Boolean): AccessCodeRecoveryStatus {
|
||||
return if (enabled) Enabled else Disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Error(val value: String) {
|
||||
object App : Error("App Error")
|
||||
object CardSdk : Error("Card Sdk Error")
|
||||
object BlockchainSdk : Error("Blockchain Sdk Error")
|
||||
}
|
||||
|
||||
sealed class ScannedFrom(val value: String) {
|
||||
object Introduction : ScannedFrom("Introduction")
|
||||
object Main : ScannedFrom("Main")
|
||||
object SignIn : ScannedFrom("Sign In")
|
||||
object MyWallets : ScannedFrom("My Wallets")
|
||||
}
|
||||
|
||||
sealed class TxSentFrom(val value: String) {
|
||||
data class Send(
|
||||
override val blockchain: String,
|
||||
override val token: String,
|
||||
override val feeType: FeeType,
|
||||
) : TxSentFrom("Send"), TxData
|
||||
|
||||
data class Swap(
|
||||
override val blockchain: String,
|
||||
override val token: String,
|
||||
override val feeType: FeeType,
|
||||
) : TxSentFrom("Swap"), TxData
|
||||
|
||||
data class Approve(
|
||||
override val blockchain: String,
|
||||
override val token: String,
|
||||
override val feeType: FeeType,
|
||||
val permissionType: String,
|
||||
) : TxSentFrom("Approve"), TxData
|
||||
|
||||
object WalletConnect : TxSentFrom("WalletConnect")
|
||||
object Sell : TxSentFrom("Sell")
|
||||
}
|
||||
|
||||
sealed interface TxData {
|
||||
val blockchain: String
|
||||
val token: String
|
||||
val feeType: FeeType
|
||||
}
|
||||
|
||||
sealed class FeeType(val value: String) {
|
||||
object Fixed : FeeType("Fixed")
|
||||
object Min : FeeType("Min")
|
||||
object Normal : FeeType("Normal")
|
||||
object Max : FeeType("Max")
|
||||
|
||||
companion object {
|
||||
fun fromString(feeType: String): FeeType {
|
||||
return when (feeType) {
|
||||
Min.value -> Min
|
||||
Normal.value -> Normal
|
||||
Max.value -> Max
|
||||
Fixed.value -> Fixed
|
||||
else -> Fixed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class WalletCreationType(val value: String) {
|
||||
object PrivateKey : WalletCreationType("Private key")
|
||||
object NewSeed : WalletCreationType("New seed")
|
||||
object SeedImport : WalletCreationType("Seed import")
|
||||
}
|
||||
|
||||
companion object Key {
|
||||
const val BLOCKCHAIN = "blockchain"
|
||||
const val TOKEN = "Token"
|
||||
const val SOURCE = "Source"
|
||||
const val BALANCE = "Balance"
|
||||
const val BATCH = "Batch"
|
||||
const val FEE_TYPE = "Fee Type"
|
||||
const val PERMISSION_TYPE = "Permission Type"
|
||||
const val PRODUCT_TYPE = "Product Type"
|
||||
const val FIRMWARE = "Firmware"
|
||||
const val CURRENCY = "Currency"
|
||||
const val ERROR_DESCRIPTION = "Error Description"
|
||||
const val ERROR_CODE = "Error Code"
|
||||
const val ERROR_KEY = "Error Key"
|
||||
const val CREATION_TYPE = "Creation type"
|
||||
const val DAPP_NAME = "DApp Name"
|
||||
const val DAPP_URL = "DApp Url"
|
||||
const val METHOD_NAME = "Method Name"
|
||||
const val VALIDATION = "Validation"
|
||||
const val BLOCKCHAIN_EXCEPTION_HOST = "exception_host"
|
||||
const val BLOCKCHAIN_SELECTED_HOST = "selected_host"
|
||||
}
|
||||
}
|
||||
|
|
@ -11,8 +11,11 @@ dependencies {
|
|||
/** Project */
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.libs.auth)
|
||||
implementation(projects.domain.appTheme.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.txhistory.models)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(deps.tangem.blockchain)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.datasource.api.common
|
||||
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonReader
|
||||
import com.squareup.moshi.JsonWriter
|
||||
import com.squareup.moshi.ToJson
|
||||
import org.joda.time.LocalDate
|
||||
import org.joda.time.format.DateTimeFormat
|
||||
|
||||
class LocalDateAdapter : JsonAdapter<LocalDate>() {
|
||||
|
||||
private val formatter = DateTimeFormat.forPattern("yyyy-MM-dd")
|
||||
|
||||
@FromJson
|
||||
override fun fromJson(reader: JsonReader): LocalDate? {
|
||||
val dateString = reader.nextString()
|
||||
return LocalDate.parse(dateString, formatter)
|
||||
}
|
||||
|
||||
@ToJson
|
||||
override fun toJson(writer: JsonWriter, value: LocalDate?) {
|
||||
if (value != null) {
|
||||
writer.value(formatter.print(value))
|
||||
} else {
|
||||
writer.nullValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.datasource.api.common.response
|
||||
|
||||
/**
|
||||
* Represents the possible responses from an API request.
|
||||
*
|
||||
* @param T The type of the data that is expected in a successful response.
|
||||
*/
|
||||
sealed class ApiResponse<T : Any> {
|
||||
|
||||
/**
|
||||
* Represents a successful response from the API.
|
||||
*
|
||||
* @property data The data returned by the API.
|
||||
*/
|
||||
data class Success<T : Any>(val data: T) : ApiResponse<T>()
|
||||
|
||||
/**
|
||||
* Represents an error response or failure from the API.
|
||||
*
|
||||
* @property cause The cause of the error.
|
||||
*/
|
||||
data class Error(val cause: ApiResponseError) : ApiResponse<Nothing>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps data in a [ApiResponse.Success] instance.
|
||||
*
|
||||
* @param data The data to wrap.
|
||||
* @return A [ApiResponse.Success] instance containing the provided data.
|
||||
*/
|
||||
internal fun <T : Any> apiSuccess(data: T): ApiResponse<T> = ApiResponse.Success(data)
|
||||
|
||||
/**
|
||||
* Wraps an [ApiResponseError] in a [ApiResponse.Error] instance.
|
||||
*
|
||||
* @param cause The error to wrap.
|
||||
* @return A [ApiResponse.Error] instance containing the provided error.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal fun <T : Any> apiError(cause: ApiResponseError): ApiResponse<T> = ApiResponse.Error(cause) as ApiResponse<T>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.datasource.api.common.response
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.CallAdapter
|
||||
import java.lang.reflect.Type
|
||||
|
||||
internal class ApiResponseCallAdapter(
|
||||
private val resultType: Type,
|
||||
) : CallAdapter<Type, Call<ApiResponse<Type>>> {
|
||||
|
||||
override fun responseType(): Type = resultType
|
||||
|
||||
override fun adapt(call: Call<Type>): Call<ApiResponse<Type>> {
|
||||
return ApiResponseCallDelegate(call)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.datasource.api.common.response
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.CallAdapter
|
||||
import retrofit2.Retrofit
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
|
||||
internal class ApiResponseCallAdapterFactory private constructor() : CallAdapter.Factory() {
|
||||
|
||||
override fun get(returnType: Type, annotations: Array<out Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {
|
||||
if (getRawType(returnType) != Call::class.java) {
|
||||
return null
|
||||
}
|
||||
|
||||
val callType = getParameterUpperBound(0, returnType as ParameterizedType)
|
||||
if (getRawType(callType) != ApiResponse::class.java) {
|
||||
return null
|
||||
}
|
||||
|
||||
val resultType = getParameterUpperBound(0, callType as ParameterizedType)
|
||||
return ApiResponseCallAdapter(resultType)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun create() = ApiResponseCallAdapterFactory()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.datasource.api.common.response
|
||||
|
||||
import okhttp3.Request
|
||||
import okio.Timeout
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
internal class ApiResponseCallDelegate<T : Any>(
|
||||
private val wrappedCall: Call<T>,
|
||||
) : Call<ApiResponse<T>> {
|
||||
|
||||
override fun enqueue(callback: Callback<ApiResponse<T>>) {
|
||||
wrappedCall.enqueue(ApiResponseCallback(callback))
|
||||
}
|
||||
|
||||
override fun execute(): Response<ApiResponse<T>> = throw NotImplementedError()
|
||||
override fun clone(): Call<ApiResponse<T>> = ApiResponseCallDelegate(wrappedCall.clone())
|
||||
override fun request(): Request = wrappedCall.request()
|
||||
override fun timeout(): Timeout = wrappedCall.timeout()
|
||||
override fun isExecuted(): Boolean = wrappedCall.isExecuted
|
||||
override fun isCanceled(): Boolean = wrappedCall.isCanceled
|
||||
override fun cancel() { wrappedCall.cancel() }
|
||||
|
||||
private inner class ApiResponseCallback(
|
||||
private val responseCallback: Callback<ApiResponse<T>>,
|
||||
) : Callback<T> {
|
||||
|
||||
override fun onResponse(call: Call<T>, response: Response<T>) {
|
||||
val safeResponse = response.toSafeApiResponse()
|
||||
|
||||
responseCallback.onResponse(this@ApiResponseCallDelegate, Response.success(safeResponse))
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<T>, t: Throwable) {
|
||||
val e = if (t.isNetworkException()) {
|
||||
ApiResponseError.NetworkException
|
||||
} else {
|
||||
ApiResponseError.UnknownException(t)
|
||||
}
|
||||
val safeResponse = apiError<T>(e)
|
||||
|
||||
responseCallback.onResponse(this@ApiResponseCallDelegate, Response.success(safeResponse))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.tangem.datasource.api.common.response
|
||||
|
||||
/**
|
||||
* Represents the possible errors that can occur during an API request.
|
||||
*/
|
||||
sealed class ApiResponseError : Exception() {
|
||||
|
||||
/**
|
||||
* Represents an HTTP exception, which typically occurs when the server responds
|
||||
* with a non-2xx HTTP status code.
|
||||
*
|
||||
* @property code The HTTP status code.
|
||||
* @property message A human-readable message describing the error.
|
||||
*/
|
||||
data class HttpException(val code: Code, override val message: String?) : ApiResponseError() {
|
||||
|
||||
// region Error Codes
|
||||
enum class Code(val code: Int) {
|
||||
// 4xx Server Errors
|
||||
BAD_REQUEST(code = 400),
|
||||
UNAUTHORIZED(code = 401),
|
||||
PAYMENT_REQUIRED(code = 402),
|
||||
FORBIDDEN(code = 403),
|
||||
NOT_FOUND(code = 404),
|
||||
METHOD_NOT_ALLOWED(code = 405),
|
||||
NOT_ACCEPTABLE(code = 406),
|
||||
PROXY_AUTHENTICATION_REQUIRED(code = 407),
|
||||
REQUEST_TIMEOUT(code = 408),
|
||||
CONFLICT(code = 409),
|
||||
GONE(code = 410),
|
||||
LENGTH_REQUIRED(code = 411),
|
||||
PRECONDITION_FAILED(code = 412),
|
||||
PAYLOAD_TOO_LARGE(code = 413),
|
||||
URI_TOO_LONG(code = 414),
|
||||
UNSUPPORTED_MEDIA_TYPE(code = 415),
|
||||
RANGE_NOT_SATISFIABLE(code = 416),
|
||||
EXPECTATION_FAILED(code = 417),
|
||||
IM_A_TEAPOT(code = 418), // Not an error, but an April Fools' joke from RFC 2324
|
||||
UNPROCESSABLE_ENTITY(code = 422),
|
||||
LOCKED(code = 423),
|
||||
FAILED_DEPENDENCY(code = 424),
|
||||
TOO_EARLY(code = 425),
|
||||
UPGRADE_REQUIRED(code = 426),
|
||||
PRECONDITION_REQUIRED(code = 428),
|
||||
TOO_MANY_REQUESTS(code = 429),
|
||||
REQUEST_HEADER_FIELDS_TOO_LARGE(code = 431),
|
||||
UNAVAILABLE_FOR_LEGAL_REASONS(code = 451),
|
||||
// 5xx Server Errors
|
||||
INTERNAL_SERVER_ERROR(code = 500),
|
||||
NOT_IMPLEMENTED(code = 501),
|
||||
BAD_GATEWAY(code = 502),
|
||||
SERVICE_UNAVAILABLE(code = 503),
|
||||
GATEWAY_TIMEOUT(code = 504),
|
||||
HTTP_VERSION_NOT_SUPPORTED(code = 505),
|
||||
VARIANT_ALSO_NEGOTIATES(code = 506),
|
||||
INSUFFICIENT_STORAGE(code = 507),
|
||||
LOOP_DETECTED(code = 508),
|
||||
NOT_EXTENDED(code = 510),
|
||||
NETWORK_AUTHENTICATION_REQUIRED(code = 511),
|
||||
;
|
||||
|
||||
override fun toString(): String = "$code - $name"
|
||||
|
||||
companion object {
|
||||
val values = values()
|
||||
}
|
||||
}
|
||||
// endregion Error Codes
|
||||
}
|
||||
|
||||
/** Represents a network error, typically when there's no connectivity. */
|
||||
object NetworkException : ApiResponseError()
|
||||
|
||||
/**
|
||||
* Represents an unexpected exception that doesn't fall into one of the other categories.
|
||||
*
|
||||
* @property cause The exception that caused this error.
|
||||
*/
|
||||
data class UnknownException(override val cause: Throwable) : ApiResponseError()
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.datasource.api.common.response
|
||||
|
||||
fun <T : Any> ApiResponse<T>.getOrThrow(): T = when (this) {
|
||||
is ApiResponse.Error -> throw cause
|
||||
is ApiResponse.Success -> data
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.datasource.api.common.response
|
||||
|
||||
import retrofit2.Response
|
||||
import java.net.ConnectException
|
||||
import java.net.UnknownHostException
|
||||
import javax.net.ssl.SSLHandshakeException
|
||||
|
||||
internal fun <T : Any> Response<T>.toSafeApiResponse(): ApiResponse<T> {
|
||||
val body = body()
|
||||
|
||||
return if (isSuccessful && body != null) {
|
||||
apiSuccess(body)
|
||||
} else {
|
||||
val code = ApiResponseError.HttpException.Code.values
|
||||
.firstOrNull { it.code == code() }
|
||||
val e = if (code == null) {
|
||||
ApiResponseError.UnknownException(IllegalArgumentException("Unknown error status code: ${code()}"))
|
||||
} else {
|
||||
ApiResponseError.HttpException(code, message())
|
||||
}
|
||||
|
||||
apiError(e)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Throwable.isNetworkException(): Boolean = when (this) {
|
||||
is ConnectException,
|
||||
is UnknownHostException,
|
||||
is SSLHandshakeException,
|
||||
-> true
|
||||
else -> false
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.datasource.api.tangemTech
|
||||
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.*
|
||||
import retrofit2.http.*
|
||||
|
||||
|
|
@ -25,13 +26,13 @@ interface TangemTechApi {
|
|||
suspend fun getRates(@Query("currencyId") currencyId: String, @Query("coinIds") coinIds: String): RatesResponse
|
||||
|
||||
@GET("currencies")
|
||||
suspend fun getCurrencyList(): CurrenciesResponse
|
||||
suspend fun getCurrencyList(): ApiResponse<CurrenciesResponse>
|
||||
|
||||
@GET("geo")
|
||||
suspend fun getUserCountryCode(): GeoResponse
|
||||
|
||||
@GET("user-tokens/{user-id}")
|
||||
suspend fun getUserTokens(@Path(value = "user-id") userId: String): UserTokensResponse
|
||||
suspend fun getUserTokens(@Path(value = "user-id") userId: String): ApiResponse<UserTokensResponse>
|
||||
|
||||
@PUT("user-tokens/{user-id}")
|
||||
suspend fun saveUserTokens(@Path(value = "user-id") userId: String, @Body userTokens: UserTokensResponse)
|
||||
|
|
@ -66,5 +67,5 @@ interface TangemTechApi {
|
|||
@Query("currencyId") currencyId: String,
|
||||
@Query("coinIds") coinIds: String,
|
||||
@Query("fields") fields: String = "price,priceChange24h,lastUpdatedAt",
|
||||
): QuotesResponse
|
||||
): ApiResponse<QuotesResponse>
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.datasource.api.tangemTech
|
||||
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
|
||||
import com.tangem.datasource.utils.RequestHeader
|
||||
import com.tangem.datasource.utils.RequestHeader.AuthenticationHeader
|
||||
import com.tangem.datasource.utils.RequestHeader.CacheControlHeader
|
||||
|
|
@ -29,6 +30,7 @@ object TangemTechService {
|
|||
val headers = mutableListOf<RequestHeader>(CacheControlHeader).apply { header?.let(::add) }
|
||||
return Retrofit.Builder()
|
||||
.addConverterFactory(MoshiConverter.networkMoshiConverter)
|
||||
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
|
||||
.baseUrl(TANGEM_TECH_BASE_URL)
|
||||
.client(
|
||||
OkHttpClient.Builder()
|
||||
|
|
|
|||
|
|
@ -10,10 +10,8 @@ data class QuotesResponse(
|
|||
|
||||
data class Quote(
|
||||
@Json(name = "price")
|
||||
val price: BigDecimal,
|
||||
val price: BigDecimal?,
|
||||
@Json(name = "priceChange24h")
|
||||
val priceChange: BigDecimal,
|
||||
@Json(name = "lastUpdatedAt")
|
||||
val lastUpdated: String,
|
||||
val priceChange: BigDecimal?,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,14 +1,12 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import org.joda.time.LocalDate
|
||||
|
||||
/**
|
||||
* Main response class for referral API
|
||||
* contains all necessary info about users program status
|
||||
*/
|
||||
data class ReferralResponse(
|
||||
@Json(name = "conditions") val conditions: Conditions,
|
||||
@Json(name = "referral") val referral: Referral?,
|
||||
@Json(name = "expectedAwards") val expectedAwards: ExpectedAwards?,
|
||||
) {
|
||||
|
||||
data class Conditions(
|
||||
|
|
@ -45,4 +43,16 @@ data class ReferralResponse(
|
|||
@Json(name = "walletsPurchased") val walletsPurchased: Int,
|
||||
@Json(name = "termsAcceptedAt") val termsAcceptedAt: String?,
|
||||
)
|
||||
|
||||
data class ExpectedAwards(
|
||||
@Json(name = "numberOfWallets") val numberOfWallets: Int,
|
||||
@Json(name = "list") val list: List<AwardItem>,
|
||||
) {
|
||||
|
||||
data class AwardItem(
|
||||
@Json(name = "currency") val currency: String,
|
||||
@Json(name = "paymentDate") val paymentDate: LocalDate,
|
||||
@Json(name = "amount") val amount: Int,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,8 @@ import com.tangem.datasource.local.appcurrency.AvailableAppCurrenciesStore
|
|||
import com.tangem.datasource.local.appcurrency.SelectedAppCurrencyStore
|
||||
import com.tangem.datasource.local.appcurrency.implementation.DefaultAvailableAppCurrenciesStore
|
||||
import com.tangem.datasource.local.appcurrency.implementation.DefaultSelectedAppCurrencyStore
|
||||
import com.tangem.datasource.local.datastore.JsonSharedPreferencesDataStore
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.datastore.SharedPreferencesDataStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -35,7 +35,7 @@ internal object AppCurrencyDataModule {
|
|||
@NetworkMoshi moshi: Moshi,
|
||||
): SelectedAppCurrencyStore {
|
||||
return DefaultSelectedAppCurrencyStore(
|
||||
dataStore = SharedPreferencesDataStore(
|
||||
dataStore = JsonSharedPreferencesDataStore(
|
||||
preferencesName = "selected_app_currency",
|
||||
context = context,
|
||||
adapter = moshi.adapter(CurrenciesResponse.Currency::class.java),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.local.apptheme.AppThemeModeStore
|
||||
import com.tangem.datasource.local.apptheme.DefaultAppThemeModeStore
|
||||
import com.tangem.datasource.local.datastore.JsonSharedPreferencesDataStore
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object AppThemeModeDataModule {
|
||||
|
||||
@Provides
|
||||
fun provideAppThemeModeStore(@ApplicationContext context: Context, @NetworkMoshi moshi: Moshi): AppThemeModeStore {
|
||||
return DefaultAppThemeModeStore(
|
||||
dataStore = JsonSharedPreferencesDataStore(
|
||||
preferencesName = "app_theme",
|
||||
context = context,
|
||||
adapter = moshi.adapter(AppThemeMode::class.java),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.datasource.local.card.DefaultUsedCardsStore
|
||||
import com.tangem.datasource.local.card.UsedCardInfo
|
||||
import com.tangem.datasource.local.card.UsedCardsStore
|
||||
import com.tangem.datasource.local.datastore.JsonSharedPreferencesDataStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object CardDataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUsedCardsStore(@ApplicationContext context: Context, @NetworkMoshi moshi: Moshi): UsedCardsStore {
|
||||
return DefaultUsedCardsStore(
|
||||
store = JsonSharedPreferencesDataStore(
|
||||
preferencesName = "tapPrefs",
|
||||
context = context,
|
||||
adapter = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, UsedCardInfo::class.java),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.local.appcurrency.BalanceHidingSettingsStore
|
||||
import com.tangem.datasource.local.appcurrency.implementation.BalanceStateHidingSettingsStore
|
||||
import com.tangem.datasource.local.datastore.JsonSharedPreferencesDataStore
|
||||
import com.tangem.domain.balancehiding.BalanceHidingSettings
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object HiddenBalanceDataModule {
|
||||
|
||||
@Provides
|
||||
fun provideHiddenBalanceStateStore(
|
||||
@ApplicationContext context: Context,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
): BalanceHidingSettingsStore {
|
||||
return BalanceStateHidingSettingsStore(
|
||||
dataStore = JsonSharedPreferencesDataStore(
|
||||
preferencesName = "balance_hiding_settings",
|
||||
context = context,
|
||||
adapter = moshi.adapter(BalanceHidingSettings::class.java),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.token.DefaultUserMarketCoinsStore
|
||||
import com.tangem.datasource.local.token.UserMarketCoinsStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object MarketCoinsStoreModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUserMarketCoinsStore(): UserMarketCoinsStore {
|
||||
return DefaultUserMarketCoinsStore(dataStore = RuntimeDataStore())
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.squareup.moshi.Moshi
|
|||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.datasource.api.common.BigDecimalAdapter
|
||||
import com.tangem.datasource.api.common.LocalDateAdapter
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -20,6 +21,7 @@ class MoshiModule {
|
|||
fun provideNetworkMoshi(): Moshi {
|
||||
return Moshi.Builder()
|
||||
.add(BigDecimalAdapter())
|
||||
.add(LocalDateAdapter())
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
|
||||
import com.tangem.datasource.api.paymentology.PaymentologyApi
|
||||
import com.tangem.datasource.api.promotion.PromotionApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
|
|
@ -8,7 +9,6 @@ import com.tangem.datasource.utils.RequestHeader.*
|
|||
import com.tangem.datasource.utils.addHeaders
|
||||
import com.tangem.datasource.utils.allowLogging
|
||||
import com.tangem.lib.auth.AuthProvider
|
||||
import com.tangem.lib.auth.BuildConfig
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -28,6 +28,7 @@ class NetworkModule {
|
|||
fun provideTangemTechApi(@NetworkMoshi moshi: Moshi): TangemTechApi {
|
||||
return Retrofit.Builder()
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
|
||||
.baseUrl(PROD_TANGEM_TECH_BASE_URL)
|
||||
.client(
|
||||
OkHttpClient.Builder()
|
||||
|
|
@ -76,7 +77,7 @@ class NetworkModule {
|
|||
private fun createBasePromotionRetrofit(okHttpClient: OkHttpClient, moshi: Moshi): PromotionApi {
|
||||
return Retrofit.Builder()
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.baseUrl(if (BuildConfig.DEBUG) DEV_TANGEM_TECH_BASE_URL else PROD_TANGEM_TECH_BASE_URL)
|
||||
.baseUrl(PROD_TANGEM_TECH_BASE_URL)
|
||||
.client(okHttpClient)
|
||||
.build()
|
||||
.create(PromotionApi::class.java)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.network.DefaultNetworksStatusesStore
|
||||
import com.tangem.datasource.local.network.NetworksStatusesStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object NetworksStatusesStoreModule {
|
||||
|
||||
@Provides
|
||||
fun provideNetworksStatusesStore(): NetworksStatusesStore {
|
||||
return DefaultNetworksStatusesStore(
|
||||
dataStore = RuntimeDataStore(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,11 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.local.datastore.SharedPreferencesDataStore
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.quote.DefaultQuotesStore
|
||||
import com.tangem.datasource.local.quote.QuotesStore
|
||||
import com.tangem.datasource.local.quote.model.StoredQuote
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -19,13 +15,9 @@ internal object QuotesStoreModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideQuotesStore(@ApplicationContext context: Context, @NetworkMoshi moshi: Moshi): QuotesStore {
|
||||
fun provideQuotesStore(): QuotesStore {
|
||||
return DefaultQuotesStore(
|
||||
dataStore = SharedPreferencesDataStore(
|
||||
preferencesName = "quotes",
|
||||
context = context,
|
||||
adapter = moshi.adapter(StoredQuote::class.java),
|
||||
),
|
||||
dataStore = RuntimeDataStore(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.datasource.local.datastore.BooleanSharedPreferencesDataStore
|
||||
import com.tangem.datasource.local.datastore.IntSharedPreferencesDataStore
|
||||
import com.tangem.datasource.local.datastore.LongSharedPreferencesDataStore
|
||||
import com.tangem.datasource.local.settings.*
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object SettingsDataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppLaunchCountStore(@ApplicationContext context: Context): AppLaunchCountStore {
|
||||
return DefaultAppLaunchCountStore(
|
||||
store = IntSharedPreferencesDataStore(preferencesName = "tapPrefs", context = context),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppRatingShowingCountStore(@ApplicationContext context: Context): AppRatingShowingCountStore {
|
||||
return DefaultAppRatingShowingCountStore(
|
||||
store = IntSharedPreferencesDataStore(preferencesName = "tapPrefs", context = context),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFundsFoundDateInMillisStore(@ApplicationContext context: Context): FundsFoundDateInMillisStore {
|
||||
return DefaultFundsFoundDateInMillisStore(
|
||||
store = LongSharedPreferencesDataStore(preferencesName = "tapPrefs", context = context),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUserInteractingStatusStore(@ApplicationContext context: Context): UserInteractingStatusStore {
|
||||
return DefaultUserInteractingStatusStore(
|
||||
store = BooleanSharedPreferencesDataStore(preferencesName = "tapPrefs", context = context),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.txhistory.DefaultTxHistoryItemsStore
|
||||
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object TxHistoryItemsStoreModule {
|
||||
|
||||
@Provides
|
||||
fun provideTxHistoryItemsStore(): TxHistoryItemsStore {
|
||||
return DefaultTxHistoryItemsStore(
|
||||
dataStore = RuntimeDataStore(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.datasource.local.datastore.BooleanSharedPreferencesDataStore
|
||||
import com.tangem.datasource.local.settings.*
|
||||
import com.tangem.datasource.local.userwallet.DefaultShouldSaveUserWalletStore
|
||||
import com.tangem.datasource.local.userwallet.ShouldSaveUserWalletStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object WalletsDataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideShouldSaveUserWalletsStore(@ApplicationContext context: Context): ShouldSaveUserWalletStore {
|
||||
return DefaultShouldSaveUserWalletStore(
|
||||
store = BooleanSharedPreferencesDataStore(preferencesName = "tapPrefs", context = context),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.datasource.local.appcurrency
|
||||
|
||||
import com.tangem.domain.balancehiding.BalanceHidingSettings
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface BalanceHidingSettingsStore {
|
||||
|
||||
fun get(): Flow<BalanceHidingSettings>
|
||||
|
||||
suspend fun getSyncOrDefault(): BalanceHidingSettings
|
||||
|
||||
suspend fun store(settings: BalanceHidingSettings)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.datasource.local.appcurrency.implementation
|
||||
|
||||
import com.tangem.datasource.local.appcurrency.BalanceHidingSettingsStore
|
||||
import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.domain.balancehiding.BalanceHidingSettings
|
||||
|
||||
internal class BalanceStateHidingSettingsStore(
|
||||
dataStore: StringKeyDataStore<BalanceHidingSettings>,
|
||||
) : BalanceHidingSettingsStore, KeylessDataStoreDecorator<BalanceHidingSettings>(dataStore) {
|
||||
|
||||
override suspend fun getSyncOrDefault(): BalanceHidingSettings {
|
||||
return getSyncOrNull() ?: BalanceHidingSettings(
|
||||
isHidingEnabledInSettings = false,
|
||||
isBalanceHidden = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,4 @@ import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
|||
|
||||
internal class DefaultSelectedAppCurrencyStore(
|
||||
dataStore: StringKeyDataStore<CurrenciesResponse.Currency>,
|
||||
) : SelectedAppCurrencyStore, KeylessDataStoreDecorator<CurrenciesResponse.Currency>(dataStore) {
|
||||
override suspend fun isEmpty(): Boolean {
|
||||
return getSyncOrNull() == null
|
||||
}
|
||||
}
|
||||
) : SelectedAppCurrencyStore, KeylessDataStoreDecorator<CurrenciesResponse.Currency>(dataStore)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.datasource.local.apptheme
|
||||
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AppThemeModeStore {
|
||||
|
||||
fun get(): Flow<AppThemeMode>
|
||||
|
||||
suspend fun store(item: AppThemeMode)
|
||||
|
||||
suspend fun isEmpty(): Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.local.apptheme
|
||||
|
||||
import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
|
||||
internal class DefaultAppThemeModeStore(
|
||||
dataStore: StringKeyDataStore<AppThemeMode>,
|
||||
) : AppThemeModeStore, KeylessDataStoreDecorator<AppThemeMode>(dataStore)
|
||||
|
|
@ -10,5 +10,7 @@ interface CacheKeysStore {
|
|||
|
||||
suspend fun remove(key: String)
|
||||
|
||||
suspend fun remove(keys: Collection<String>)
|
||||
|
||||
suspend fun clear()
|
||||
}
|
||||
|
|
@ -2,15 +2,10 @@ package com.tangem.datasource.local.cache
|
|||
|
||||
import com.tangem.datasource.local.cache.model.CacheKey
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
|
||||
|
||||
internal class DefaultCacheKeysStore(
|
||||
dataStore: StringKeyDataStore<CacheKey>,
|
||||
) : CacheKeysStore, StringKeyDataStoreDecorator<String, CacheKey>(dataStore) {
|
||||
|
||||
override fun provideStringKey(key: String): String {
|
||||
return key
|
||||
}
|
||||
) : CacheKeysStore, StringKeyDataStore<CacheKey> by dataStore {
|
||||
|
||||
override suspend fun store(key: CacheKey) {
|
||||
store(key.id, key)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.datasource.local.card
|
||||
|
||||
data class UsedCardInfo(
|
||||
val cardId: String,
|
||||
val isScanned: Boolean = false,
|
||||
val isActivationStarted: Boolean = false,
|
||||
val isActivationFinished: Boolean = false,
|
||||
)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.local.card
|
||||
|
||||
import com.tangem.datasource.local.datastore.SharedPreferencesDataStore
|
||||
import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface UsedCardsStore {
|
||||
|
||||
fun get(): Flow<List<UsedCardInfo>>
|
||||
|
||||
suspend fun getSyncOrNull(): List<UsedCardInfo>?
|
||||
|
||||
suspend fun store(item: List<UsedCardInfo>)
|
||||
}
|
||||
|
||||
internal class DefaultUsedCardsStore(
|
||||
store: SharedPreferencesDataStore<List<UsedCardInfo>>,
|
||||
) : UsedCardsStore, KeylessDataStoreDecorator<List<UsedCardInfo>>(
|
||||
wrappedDataStore = store,
|
||||
key = "usedCardsInfo_v2",
|
||||
)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.datasource.local.datastore
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
|
||||
internal class BooleanSharedPreferencesDataStore(
|
||||
preferencesName: String,
|
||||
context: Context,
|
||||
) : SharedPreferencesDataStore<Boolean>(preferencesName, context) {
|
||||
|
||||
override fun getByKey(key: String): Boolean = sharedPreferences.getBoolean(key, false)
|
||||
|
||||
override fun storeByKey(key: String, value: Boolean) {
|
||||
sharedPreferences.edit { putBoolean(key, value) }
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,10 @@ import com.squareup.moshi.JsonAdapter
|
|||
import com.tangem.datasource.files.FileReader
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.datasource.local.datastore.utils.Trigger
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import timber.log.Timber
|
||||
|
||||
@Deprecated("Use shared preferences data store instead")
|
||||
|
|
@ -14,6 +17,14 @@ internal class FileDataStore<Value : Any>(
|
|||
) : StringKeyDataStore<Value> {
|
||||
|
||||
private val writeTrigger = Trigger()
|
||||
override suspend fun isEmpty(): Boolean {
|
||||
val e = NotImplementedError("`isEmpty()` function not implemented for `FileDataStore`")
|
||||
Timber.e(e)
|
||||
|
||||
throw e
|
||||
}
|
||||
|
||||
override suspend fun contains(key: String): Boolean = getSyncOrNull(key) != null
|
||||
|
||||
override fun get(key: String): Flow<Value> {
|
||||
return writeTrigger
|
||||
|
|
@ -33,16 +44,16 @@ internal class FileDataStore<Value : Any>(
|
|||
return getInternal(key)
|
||||
}
|
||||
|
||||
override suspend fun getAllSyncOrNull(): List<Value> {
|
||||
override suspend fun getAllSyncOrNull(): List<Value>? {
|
||||
val e = NotImplementedError("`getAllSyncOrNull()` function not implemented for `FileDataStore`")
|
||||
Timber.e(e)
|
||||
|
||||
throw e
|
||||
}
|
||||
|
||||
override suspend fun store(key: String, item: Value) {
|
||||
override suspend fun store(key: String, value: Value) {
|
||||
try {
|
||||
val json = adapter.toJson(item)
|
||||
val json = adapter.toJson(value)
|
||||
|
||||
fileReader.rewriteFile(json, key)
|
||||
writeTrigger.trigger()
|
||||
|
|
@ -51,8 +62,8 @@ internal class FileDataStore<Value : Any>(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun store(items: Map<String, Value>) {
|
||||
items.forEach { (key, item) ->
|
||||
override suspend fun store(values: Map<String, Value>) {
|
||||
values.forEach { (key, item) ->
|
||||
store(key, item)
|
||||
}
|
||||
}
|
||||
|
|
@ -62,6 +73,13 @@ internal class FileDataStore<Value : Any>(
|
|||
writeTrigger.trigger()
|
||||
}
|
||||
|
||||
override suspend fun remove(keys: Collection<String>) {
|
||||
val e = NotImplementedError("`remove(keys)` function not implemented for `FileDataStore`")
|
||||
Timber.e(e)
|
||||
|
||||
throw e
|
||||
}
|
||||
|
||||
override suspend fun clear() {
|
||||
val e = NotImplementedError("`clear()` function not implemented for `FileDataStore`")
|
||||
Timber.e(e)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.datasource.local.datastore
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
|
||||
internal class IntSharedPreferencesDataStore(
|
||||
preferencesName: String,
|
||||
context: Context,
|
||||
) : SharedPreferencesDataStore<Int>(preferencesName, context) {
|
||||
|
||||
override fun getByKey(key: String): Int = sharedPreferences.getInt(key, 0)
|
||||
|
||||
override fun storeByKey(key: String, value: Int) {
|
||||
sharedPreferences.edit { putInt(key, value) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.datasource.local.datastore
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
|
||||
internal class JsonSharedPreferencesDataStore<Value : Any>(
|
||||
preferencesName: String,
|
||||
context: Context,
|
||||
private val adapter: JsonAdapter<Value>,
|
||||
) : SharedPreferencesDataStore<Value>(preferencesName, context) {
|
||||
|
||||
override fun getByKey(key: String): Value? {
|
||||
val json = sharedPreferences.getString(key, null) ?: return null
|
||||
|
||||
return adapter.fromJson(json)
|
||||
}
|
||||
|
||||
override fun storeByKey(key: String, value: Value) {
|
||||
val json = adapter.toJson(value)
|
||||
|
||||
sharedPreferences.edit { putString(key, json) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.datasource.local.datastore
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
|
||||
internal class LongSharedPreferencesDataStore(
|
||||
preferencesName: String,
|
||||
context: Context,
|
||||
) : SharedPreferencesDataStore<Long>(preferencesName, context) {
|
||||
|
||||
override fun getByKey(key: String): Long = sharedPreferences.getLong(key, 0)
|
||||
|
||||
override fun storeByKey(key: String, value: Long) {
|
||||
sharedPreferences.edit { putLong(key, value) }
|
||||
}
|
||||
}
|
||||
|
|
@ -5,53 +5,74 @@ import kotlinx.coroutines.flow.*
|
|||
|
||||
internal class RuntimeDataStore<Data : Any> : StringKeyDataStore<Data> {
|
||||
|
||||
private val store = MutableStateFlow<HashMap<String, Data>>(hashMapOf())
|
||||
private val store: MutableSharedFlow<HashMap<String, Data>?> = MutableSharedFlow(replay = 1)
|
||||
|
||||
init {
|
||||
store.tryEmit(value = null)
|
||||
}
|
||||
|
||||
override suspend fun isEmpty(): Boolean = store.firstOrNull().isNullOrEmpty()
|
||||
|
||||
override suspend fun contains(key: String): Boolean = getSyncOrNull(key) != null
|
||||
|
||||
override fun get(key: String): Flow<Data> {
|
||||
return store
|
||||
.map { value -> value[key] }
|
||||
.filterNotNull()
|
||||
}
|
||||
|
||||
override fun getAll(): Flow<List<Data>> {
|
||||
return store.map { value -> value.values.toList() }
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(key: String): Data? {
|
||||
return store.value[key]
|
||||
}
|
||||
|
||||
override suspend fun getAllSyncOrNull(): List<Data> {
|
||||
return store.value.values.toList()
|
||||
}
|
||||
|
||||
override suspend fun store(key: String, item: Data) {
|
||||
store.update { value ->
|
||||
value[key] = item
|
||||
|
||||
value
|
||||
return store.mapNotNull { value ->
|
||||
value?.get(key)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun store(items: Map<String, Data>) {
|
||||
store.update { value ->
|
||||
items.forEach { (key, item) ->
|
||||
value[key] = item
|
||||
}
|
||||
override fun getAll(): Flow<List<Data>> {
|
||||
return store.map { value ->
|
||||
value?.values?.toList().orEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
value
|
||||
override suspend fun getSyncOrNull(key: String): Data? {
|
||||
return store.firstOrNull()?.get(key)
|
||||
}
|
||||
|
||||
override suspend fun getAllSyncOrNull(): List<Data>? {
|
||||
return store.firstOrNull()?.values?.toList()
|
||||
}
|
||||
|
||||
override suspend fun store(key: String, value: Data) {
|
||||
updateValue { storedValue ->
|
||||
storedValue[key] = value
|
||||
|
||||
storedValue
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun store(values: Map<String, Data>) {
|
||||
updateValue { storedValue ->
|
||||
storedValue.putAll(values)
|
||||
|
||||
storedValue
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun remove(key: String) {
|
||||
store.update { value ->
|
||||
value.remove(key)
|
||||
updateValue { storedValue ->
|
||||
storedValue.remove(key)
|
||||
|
||||
value
|
||||
storedValue
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun remove(keys: Collection<String>) {
|
||||
updateValue { value ->
|
||||
HashMap(value.filterKeys { it !in keys })
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clear() {
|
||||
store.update { hashMapOf() }
|
||||
store.emit(value = null)
|
||||
}
|
||||
|
||||
private suspend inline fun updateValue(update: (HashMap<String, Data>) -> HashMap<String, Data>) {
|
||||
val storedData = store.firstOrNull() ?: hashMapOf()
|
||||
val updatedData = update(storedData)
|
||||
|
||||
store.emit(updatedData)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,61 +4,63 @@ import android.content.Context
|
|||
import android.content.Context.MODE_PRIVATE
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.datasource.local.datastore.utils.Trigger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import timber.log.Timber
|
||||
|
||||
internal class SharedPreferencesDataStore<Value : Any>(
|
||||
internal abstract class SharedPreferencesDataStore<Value : Any>(
|
||||
preferencesName: String,
|
||||
private val context: Context,
|
||||
private val adapter: JsonAdapter<Value>,
|
||||
context: Context,
|
||||
) : StringKeyDataStore<Value> {
|
||||
|
||||
private val sharedPreferences: SharedPreferences by lazy {
|
||||
protected val sharedPreferences: SharedPreferences by lazy {
|
||||
context.getSharedPreferences(preferencesName, MODE_PRIVATE)
|
||||
}
|
||||
|
||||
private val writeTrigger = Trigger()
|
||||
|
||||
abstract fun getByKey(key: String): Value?
|
||||
|
||||
abstract fun storeByKey(key: String, value: Value)
|
||||
|
||||
override suspend fun isEmpty(): Boolean {
|
||||
return sharedPreferences.all.isEmpty()
|
||||
}
|
||||
|
||||
override suspend fun contains(key: String): Boolean {
|
||||
return sharedPreferences.contains(key)
|
||||
}
|
||||
|
||||
override fun get(key: String): Flow<Value> {
|
||||
return writeTrigger
|
||||
.map { getInternal(key) }
|
||||
.filterNotNull()
|
||||
.mapNotNull { getInternal(key) }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
override fun getAll(): Flow<List<Value>> {
|
||||
return writeTrigger
|
||||
.map { getAllInternal() }
|
||||
.distinctUntilChanged()
|
||||
throw UnsupportedOperationException("Unknown key")
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(key: String): Value? {
|
||||
return getInternal(key)
|
||||
}
|
||||
override suspend fun getSyncOrNull(key: String): Value? = getInternal(key)
|
||||
|
||||
override suspend fun getAllSyncOrNull(): List<Value> {
|
||||
return getAllInternal()
|
||||
throw UnsupportedOperationException("Unknown key")
|
||||
}
|
||||
|
||||
override suspend fun store(key: String, item: Value) {
|
||||
override suspend fun store(key: String, value: Value) {
|
||||
try {
|
||||
val json = adapter.toJson(item)
|
||||
|
||||
sharedPreferences.edit { putString(key, json) }
|
||||
storeByKey(key, value)
|
||||
writeTrigger.trigger()
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Unable to edit preferences: $key")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun store(items: Map<String, Value>) {
|
||||
items.forEach { (key, item) ->
|
||||
override suspend fun store(values: Map<String, Value>) {
|
||||
values.forEach { (key, item) ->
|
||||
store(key, item)
|
||||
}
|
||||
}
|
||||
|
|
@ -68,32 +70,29 @@ internal class SharedPreferencesDataStore<Value : Any>(
|
|||
writeTrigger.trigger()
|
||||
}
|
||||
|
||||
override suspend fun remove(keys: Collection<String>) {
|
||||
sharedPreferences.edit {
|
||||
keys.forEach { key ->
|
||||
remove(key)
|
||||
}
|
||||
}
|
||||
|
||||
writeTrigger.trigger()
|
||||
}
|
||||
|
||||
override suspend fun clear() {
|
||||
sharedPreferences.edit { clear() }
|
||||
writeTrigger.trigger()
|
||||
}
|
||||
|
||||
private fun getInternal(key: String): Value? {
|
||||
return try {
|
||||
val json = sharedPreferences.getString(key, null) ?: return null
|
||||
if (!sharedPreferences.contains(key)) return null
|
||||
|
||||
adapter.fromJson(json)
|
||||
return try {
|
||||
getByKey(key)
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Unable to get value from preferences: $key")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAllInternal(): List<Value> {
|
||||
return sharedPreferences.all.mapNotNull { (key, value) ->
|
||||
try {
|
||||
val json = value as? String ?: return@mapNotNull null
|
||||
|
||||
adapter.fromJson(json)
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Unable to convert value from JSON: $key")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,21 +2,94 @@ package com.tangem.datasource.local.datastore.core
|
|||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Represents a generic key-value data store.
|
||||
*
|
||||
* @param Key The type of the keys used to identify values in the store.
|
||||
* @param Value The type of the values stored.
|
||||
*/
|
||||
internal interface DataStore<Key : Any, Value : Any> {
|
||||
|
||||
/**
|
||||
* Checks if the data store is empty.
|
||||
*
|
||||
* @return `true` if the data store has no entries, otherwise `false`.
|
||||
*/
|
||||
suspend fun isEmpty(): Boolean
|
||||
|
||||
/**
|
||||
* Checks if the data store contains an entry with the specified key.
|
||||
*
|
||||
* @param key The key to check for presence in the store.
|
||||
* @return `true` if the key is present, otherwise `false`.
|
||||
*/
|
||||
suspend fun contains(key: Key): Boolean
|
||||
|
||||
/**
|
||||
* Retrieves a value updates associated with the given key, as a flow.
|
||||
*
|
||||
* @param key The key to look up in the store.
|
||||
* @return A flow emitting the value associated with the given key.
|
||||
*/
|
||||
fun get(key: Key): Flow<Value>
|
||||
|
||||
/**
|
||||
* Retrieves all values updates from the data store, as a flow.
|
||||
*
|
||||
* @return A flow emitting a list of all values in the store.
|
||||
*/
|
||||
fun getAll(): Flow<List<Value>>
|
||||
|
||||
/**
|
||||
* Retrieves a value associated with the given key synchronously.
|
||||
*
|
||||
* If the key does not exist, this method returns `null`.
|
||||
*
|
||||
* @param key The key to look up in the store.
|
||||
* @return The value associated with the key, or `null` if not present.
|
||||
*/
|
||||
suspend fun getSyncOrNull(key: Key): Value?
|
||||
|
||||
suspend fun getAllSyncOrNull(): List<Value>
|
||||
/**
|
||||
* Retrieves all values from the data store synchronously.
|
||||
*
|
||||
* If the store is empty, this method returns `null`.
|
||||
*
|
||||
* @return A list of all values in the store, or `null` if empty.
|
||||
*/
|
||||
suspend fun getAllSyncOrNull(): List<Value>?
|
||||
|
||||
suspend fun store(key: Key, item: Value)
|
||||
/**
|
||||
* Stores a value in the data store associated with the given key.
|
||||
*
|
||||
* @param key The key to associate with the value.
|
||||
* @param value The value to store.
|
||||
*/
|
||||
suspend fun store(key: Key, value: Value)
|
||||
|
||||
suspend fun store(items: Map<Key, Value>)
|
||||
/**
|
||||
* Stores multiple values in the data store with their associated keys.
|
||||
*
|
||||
* @param values A map of keys to values to store.
|
||||
*/
|
||||
suspend fun store(values: Map<Key, Value>)
|
||||
|
||||
/**
|
||||
* Removes a value associated with the given key from the data store.
|
||||
*
|
||||
* @param key The key of the value to remove.
|
||||
*/
|
||||
suspend fun remove(key: Key)
|
||||
|
||||
/**
|
||||
* Removes values associated with the given keys from the data store.
|
||||
*
|
||||
* @param keys Keys of values to remove.
|
||||
*/
|
||||
suspend fun remove(keys: Collection<Key>)
|
||||
|
||||
/**
|
||||
* Clears all entries from the data store.
|
||||
*/
|
||||
suspend fun clear()
|
||||
}
|
||||
|
|
@ -1,28 +1,21 @@
|
|||
package com.tangem.datasource.local.datastore.core
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
internal abstract class KeylessDataStoreDecorator<Value : Any>(
|
||||
wrappedDataStore: StringKeyDataStore<Value>,
|
||||
) : StringKeyDataStoreDecorator<Unit, Value>(wrappedDataStore) {
|
||||
private val key: String = DEFAULT_STRING_KEY,
|
||||
) : StringKeyDataStoreDecorator<String, Value>(wrappedDataStore) {
|
||||
|
||||
override fun provideStringKey(key: Unit): String {
|
||||
return STRING_KEY
|
||||
}
|
||||
override fun provideStringKey(key: String) = key
|
||||
|
||||
open fun get(): Flow<Value> {
|
||||
return get(Unit)
|
||||
}
|
||||
open fun get() = get(key)
|
||||
|
||||
open suspend fun getSyncOrNull(): Value? {
|
||||
return getSyncOrNull(Unit)
|
||||
}
|
||||
open suspend fun getSyncOrNull() = getSyncOrNull(key)
|
||||
|
||||
open suspend fun store(item: Value) {
|
||||
store(Unit, item)
|
||||
}
|
||||
open suspend fun store(item: Value) = store(key, item)
|
||||
|
||||
override suspend fun isEmpty(): Boolean = getSyncOrNull() == null
|
||||
|
||||
private companion object {
|
||||
const val STRING_KEY = "key"
|
||||
const val DEFAULT_STRING_KEY = "key"
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,10 @@ internal abstract class StringKeyDataStoreDecorator<Key : Any, Value : Any>(
|
|||
|
||||
abstract fun provideStringKey(key: Key): String
|
||||
|
||||
override suspend fun isEmpty(): Boolean = wrappedDataStore.isEmpty()
|
||||
|
||||
override suspend fun contains(key: Key): Boolean = wrappedDataStore.contains(provideStringKey(key))
|
||||
|
||||
override fun get(key: Key): Flow<Value> {
|
||||
return wrappedDataStore.get(provideStringKey(key))
|
||||
}
|
||||
|
|
@ -16,7 +20,7 @@ internal abstract class StringKeyDataStoreDecorator<Key : Any, Value : Any>(
|
|||
return wrappedDataStore.getAll()
|
||||
}
|
||||
|
||||
override suspend fun getAllSyncOrNull(): List<Value> {
|
||||
override suspend fun getAllSyncOrNull(): List<Value>? {
|
||||
return wrappedDataStore.getAllSyncOrNull()
|
||||
}
|
||||
|
||||
|
|
@ -24,13 +28,13 @@ internal abstract class StringKeyDataStoreDecorator<Key : Any, Value : Any>(
|
|||
return wrappedDataStore.getSyncOrNull(provideStringKey(key))
|
||||
}
|
||||
|
||||
override suspend fun store(key: Key, item: Value) {
|
||||
wrappedDataStore.store(provideStringKey(key), item)
|
||||
override suspend fun store(key: Key, value: Value) {
|
||||
wrappedDataStore.store(provideStringKey(key), value)
|
||||
}
|
||||
|
||||
override suspend fun store(items: Map<Key, Value>) {
|
||||
override suspend fun store(values: Map<Key, Value>) {
|
||||
wrappedDataStore.store(
|
||||
items = items.mapKeys { (key, _) -> provideStringKey(key) },
|
||||
values = values.mapKeys { (key, _) -> provideStringKey(key) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -38,6 +42,10 @@ internal abstract class StringKeyDataStoreDecorator<Key : Any, Value : Any>(
|
|||
wrappedDataStore.remove(provideStringKey(key))
|
||||
}
|
||||
|
||||
override suspend fun remove(keys: Collection<Key>) {
|
||||
wrappedDataStore.remove(keys.map(::provideStringKey))
|
||||
}
|
||||
|
||||
override suspend fun clear() {
|
||||
wrappedDataStore.clear()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.datasource.local.network
|
||||
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
internal class DefaultNetworksStatusesStore(
|
||||
dataStore: StringKeyDataStore<Set<NetworkStatus>>,
|
||||
) : NetworksStatusesStore, StringKeyDataStoreDecorator<UserWalletId, Set<NetworkStatus>>(dataStore) {
|
||||
|
||||
private val mutex = Mutex()
|
||||
|
||||
override fun provideStringKey(key: UserWalletId): String {
|
||||
return key.stringValue
|
||||
}
|
||||
|
||||
override suspend fun store(key: UserWalletId, value: NetworkStatus) {
|
||||
mutex.withLock {
|
||||
val newValues = getSyncOrNull(key)
|
||||
?.addOrReplace(value) { it.network == value.network }
|
||||
?: setOf(value)
|
||||
|
||||
store(key, newValues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.datasource.local.network
|
||||
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NetworksStatusesStore {
|
||||
|
||||
fun get(key: UserWalletId): Flow<Set<NetworkStatus>>
|
||||
|
||||
suspend fun getSyncOrNull(key: UserWalletId): Set<NetworkStatus>?
|
||||
|
||||
suspend fun store(key: UserWalletId, value: NetworkStatus)
|
||||
}
|
||||
|
|
@ -3,8 +3,9 @@ package com.tangem.datasource.local.quote
|
|||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.datasource.local.quote.model.StoredQuote
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
internal class DefaultQuotesStore(
|
||||
|
|
@ -12,16 +13,24 @@ internal class DefaultQuotesStore(
|
|||
) : QuotesStore {
|
||||
|
||||
override fun get(currenciesIds: Set<CryptoCurrency.ID>): Flow<Set<StoredQuote>> {
|
||||
val flows = currenciesIds.mapNotNull { currencyId ->
|
||||
dataStore.get(currencyId.rawCurrencyId ?: return@mapNotNull null)
|
||||
}
|
||||
return channelFlow {
|
||||
val flows = currenciesIds.mapNotNull { currencyId ->
|
||||
currencyId.rawCurrencyId?.let(dataStore::get)
|
||||
}
|
||||
|
||||
return combine(flows) { quotes -> quotes.toSet() }
|
||||
if (dataStore.isEmpty() || flows.isEmpty()) {
|
||||
send(emptySet())
|
||||
}
|
||||
|
||||
combine(flows) { quotes -> quotes.toSet() }.collect(::send)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun store(response: QuotesResponse) {
|
||||
response.quotes.forEach { (rawCurrencyId, quote) ->
|
||||
dataStore.store(rawCurrencyId, StoredQuote(rawCurrencyId, quote))
|
||||
val quotes = response.quotes.mapValues { (id, quote) ->
|
||||
StoredQuote(id, quote)
|
||||
}
|
||||
|
||||
dataStore.store(quotes)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package com.tangem.datasource.local.quote
|
|||
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import com.tangem.datasource.local.quote.model.StoredQuote
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface QuotesStore {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.local.settings
|
||||
|
||||
import com.tangem.datasource.local.datastore.IntSharedPreferencesDataStore
|
||||
import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AppLaunchCountStore {
|
||||
|
||||
fun get(): Flow<Int>
|
||||
|
||||
suspend fun getSyncOrNull(): Int?
|
||||
|
||||
suspend fun store(item: Int)
|
||||
}
|
||||
|
||||
internal class DefaultAppLaunchCountStore(
|
||||
store: IntSharedPreferencesDataStore,
|
||||
) : AppLaunchCountStore, KeylessDataStoreDecorator<Int>(
|
||||
wrappedDataStore = store,
|
||||
key = "launchCount",
|
||||
)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.local.settings
|
||||
|
||||
import com.tangem.datasource.local.datastore.IntSharedPreferencesDataStore
|
||||
import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AppRatingShowingCountStore {
|
||||
|
||||
fun get(): Flow<Int>
|
||||
|
||||
suspend fun getSyncOrNull(): Int?
|
||||
|
||||
suspend fun store(item: Int)
|
||||
}
|
||||
|
||||
internal class DefaultAppRatingShowingCountStore(
|
||||
store: IntSharedPreferencesDataStore,
|
||||
) : AppRatingShowingCountStore, KeylessDataStoreDecorator<Int>(
|
||||
wrappedDataStore = store,
|
||||
key = "showRatingDialogAtLaunchCount",
|
||||
)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.local.settings
|
||||
|
||||
import com.tangem.datasource.local.datastore.LongSharedPreferencesDataStore
|
||||
import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface FundsFoundDateInMillisStore {
|
||||
|
||||
fun get(): Flow<Long>
|
||||
|
||||
suspend fun getSyncOrNull(): Long?
|
||||
|
||||
suspend fun store(item: Long)
|
||||
}
|
||||
|
||||
internal class DefaultFundsFoundDateInMillisStore(
|
||||
store: LongSharedPreferencesDataStore,
|
||||
) : FundsFoundDateInMillisStore, KeylessDataStoreDecorator<Long>(
|
||||
wrappedDataStore = store,
|
||||
key = "fundsFoundDate",
|
||||
)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.local.settings
|
||||
|
||||
import com.tangem.datasource.local.datastore.BooleanSharedPreferencesDataStore
|
||||
import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface UserInteractingStatusStore {
|
||||
|
||||
fun get(): Flow<Boolean>
|
||||
|
||||
suspend fun getSyncOrNull(): Boolean?
|
||||
|
||||
suspend fun store(item: Boolean)
|
||||
}
|
||||
|
||||
internal class DefaultUserInteractingStatusStore(
|
||||
store: BooleanSharedPreferencesDataStore,
|
||||
) : UserInteractingStatusStore, KeylessDataStoreDecorator<Boolean>(
|
||||
wrappedDataStore = store,
|
||||
key = "userWasInteractWithRating",
|
||||
)
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
internal class DefaultUserMarketCoinsStore(
|
||||
private val dataStore: StringKeyDataStore<CoinsResponse>,
|
||||
) : UserMarketCoinsStore {
|
||||
|
||||
override suspend fun getSyncOrNull(userWalletId: UserWalletId): CoinsResponse? {
|
||||
return dataStore.getSyncOrNull(userWalletId.stringValue)
|
||||
}
|
||||
|
||||
override suspend fun store(userWalletId: UserWalletId, item: CoinsResponse) {
|
||||
dataStore.store(userWalletId.stringValue, item)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface UserMarketCoinsStore {
|
||||
|
||||
suspend fun getSyncOrNull(userWalletId: UserWalletId): CoinsResponse?
|
||||
|
||||
suspend fun store(userWalletId: UserWalletId, item: CoinsResponse)
|
||||
}
|
||||
|
|
@ -10,5 +10,5 @@ interface UserTokensStore {
|
|||
|
||||
suspend fun getSyncOrNull(key: UserWalletId): UserTokensResponse?
|
||||
|
||||
suspend fun store(key: UserWalletId, item: UserTokensResponse)
|
||||
suspend fun store(key: UserWalletId, value: UserTokensResponse)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.datasource.local.txhistory
|
||||
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
|
||||
import com.tangem.domain.txhistory.models.PaginationWrapper
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
|
||||
internal class DefaultTxHistoryItemsStore(
|
||||
dataStore: StringKeyDataStore<Set<PaginationWrapper<TxHistoryItem>>>,
|
||||
) : TxHistoryItemsStore,
|
||||
StringKeyDataStoreDecorator<TxHistoryItemsStore.Key, Set<PaginationWrapper<TxHistoryItem>>>(dataStore) {
|
||||
|
||||
override fun provideStringKey(key: TxHistoryItemsStore.Key): String = key.toString()
|
||||
|
||||
override suspend fun getNextPageSyncOrNull(key: TxHistoryItemsStore.Key): Int? {
|
||||
val storedValue = getSyncOrNull(key) ?: return null
|
||||
val lastWrappedItems = storedValue.maxBy(PaginationWrapper<*>::page)
|
||||
val lastPage = lastWrappedItems.page
|
||||
|
||||
return if (lastPage <= lastWrappedItems.totalPages) {
|
||||
lastPage
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(key: TxHistoryItemsStore.Key, page: Int): PaginationWrapper<TxHistoryItem>? {
|
||||
val storedValue = getSyncOrNull(key)
|
||||
|
||||
return storedValue?.firstOrNull { it.page == page }
|
||||
}
|
||||
|
||||
override suspend fun store(key: TxHistoryItemsStore.Key, value: PaginationWrapper<TxHistoryItem>) {
|
||||
val oldValue = getSyncOrNull(key).orEmpty()
|
||||
val newValue = oldValue.addOrReplace(value) {
|
||||
it.page == value.page
|
||||
}
|
||||
|
||||
store(key, newValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.datasource.local.txhistory
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.txhistory.models.PaginationWrapper
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface TxHistoryItemsStore {
|
||||
|
||||
suspend fun getNextPageSyncOrNull(key: Key): Int?
|
||||
|
||||
suspend fun getSyncOrNull(key: Key, page: Int): PaginationWrapper<TxHistoryItem>?
|
||||
|
||||
suspend fun remove(key: Key)
|
||||
|
||||
suspend fun store(key: Key, value: PaginationWrapper<TxHistoryItem>)
|
||||
|
||||
data class Key(
|
||||
val userWalletId: UserWalletId,
|
||||
val currency: CryptoCurrency,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.datasource.local.userwallet
|
||||
|
||||
import com.tangem.datasource.local.datastore.BooleanSharedPreferencesDataStore
|
||||
import com.tangem.datasource.local.datastore.core.KeylessDataStoreDecorator
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ShouldSaveUserWalletStore {
|
||||
|
||||
fun get(): Flow<Boolean>
|
||||
|
||||
suspend fun getSyncOrNull(): Boolean?
|
||||
|
||||
suspend fun store(item: Boolean)
|
||||
}
|
||||
|
||||
internal class DefaultShouldSaveUserWalletStore(
|
||||
store: BooleanSharedPreferencesDataStore,
|
||||
) : ShouldSaveUserWalletStore, KeylessDataStoreDecorator<Boolean>(
|
||||
wrappedDataStore = store,
|
||||
key = "saveUserWallets",
|
||||
)
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
|||
import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
internal class DefaultWalletManagersStore(
|
||||
dataStore: StringKeyDataStore<List<WalletManager>>,
|
||||
|
|
@ -15,6 +16,10 @@ internal class DefaultWalletManagersStore(
|
|||
return key.stringValue
|
||||
}
|
||||
|
||||
override fun getAll(userWalletId: UserWalletId): Flow<List<WalletManager>> {
|
||||
return get(key = userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(
|
||||
userWalletId: UserWalletId,
|
||||
blockchain: Blockchain,
|
||||
|
|
@ -28,6 +33,10 @@ internal class DefaultWalletManagersStore(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getAllSync(userWalletId: UserWalletId): List<WalletManager> {
|
||||
return getSyncOrNull(userWalletId) ?: emptyList()
|
||||
}
|
||||
|
||||
override suspend fun store(userWalletId: UserWalletId, walletManager: WalletManager) {
|
||||
val walletManagers = getSyncOrNull(userWalletId)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,15 +3,20 @@ package com.tangem.datasource.local.walletmanager
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface WalletManagersStore {
|
||||
|
||||
fun getAll(userWalletId: UserWalletId): Flow<List<WalletManager>>
|
||||
|
||||
suspend fun getSyncOrNull(
|
||||
userWalletId: UserWalletId,
|
||||
blockchain: Blockchain,
|
||||
derivationPath: String?,
|
||||
): WalletManager?
|
||||
|
||||
suspend fun getAllSync(userWalletId: UserWalletId): List<WalletManager>
|
||||
|
||||
suspend fun store(userWalletId: UserWalletId, walletManager: WalletManager)
|
||||
|
||||
suspend fun clear()
|
||||
|
|
|
|||
|
|
@ -3,10 +3,6 @@
|
|||
"name": "OPTIMISM_SWAP_FEATURE_ENABLED",
|
||||
"version": "4.3.1"
|
||||
},
|
||||
{
|
||||
"name": "REDESIGNED_CUSTOM_TOKEN_SCREEN_ENABLED",
|
||||
"version": "4.7.0"
|
||||
},
|
||||
{
|
||||
"name": "NEW_CARD_SCANNING_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
@ -20,11 +16,15 @@
|
|||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "REDESIGNED_TOKEN_DETAIL_SCREEN_ENABLED",
|
||||
"name": "SHOPIFY_DYNAMIC_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "SHOPIFY_DYNAMIC_ENABLED",
|
||||
"name": "REDESIGNED_APP_CURRENCY_SELECTOR_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "DARK_THEME_ENABLED",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -35,4 +35,5 @@ enum class AppScreen(val isDialogFragment: Boolean = false) {
|
|||
Welcome,
|
||||
SaveWallet(isDialogFragment = true),
|
||||
WalletSelector(isDialogFragment = true),
|
||||
AppCurrencySelector,
|
||||
}
|
||||
|
|
@ -10,5 +10,7 @@ interface ReduxNavController {
|
|||
/** Navigate by [action] */
|
||||
fun navigate(action: NavigationAction)
|
||||
|
||||
fun popBackStack(screen: AppScreen? = null)
|
||||
|
||||
fun getBackStack(): List<AppScreen>
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<string name="alert_card_signed_transactions">Diese Karte wurde früher bereits aufgeladen und Transaktionen wurden damit signiert. Ziehen Sie eine sofortige Auszahlung aller Beträge in Betracht, wenn Sie diese Karte von einer nicht vertrauenswürdigen Quelle erhalten haben.</string>
|
||||
<string name="alert_developer_card">Die von Ihnen gescannte Karte ist eine Entwicklungskarte. Akzeptieren Sie sie nicht als Zahlungsmittel.</string>
|
||||
<string name="alert_unsupported_card">Diese Karte ist für die Zusammenarbeit mit Tangem nicht geeignet</string>
|
||||
<string name="chat_bot_name">Tangem Bot</string>
|
||||
<string name="common_accept">Akzeptieren</string>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<string name="alert_card_signed_transactions">Cette carte a déjà été rechargée et a signé des transactions avant. Envisagez la possibilité de retirer tous les fonds immédiatement si vous avez reçu cette carte d\'une source non fiable.</string>
|
||||
<string name="alert_developer_card">La carte que vous avez scannée est une carte de développement. Ne l\'acceptez pas comme paiement.</string>
|
||||
<string name="alert_unsupported_card">Cette carte n\'est pas conçue pour fonctionner avec Tangem</string>
|
||||
<string name="chat_bot_name">Tangem Bot</string>
|
||||
<string name="common_accept">J\'accepte</string>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<string name="alert_card_signed_transactions">Questa carta è già stata ricaricata e ha firmato transazioni in passato. Valuta la possibilità di prelevare immediatamente tutti i fondi se hai ricevuto questa carta da una fonte inaffidabile.</string>
|
||||
<string name="alert_developer_card">La carta che hai scansionato è una carta di sviluppo. Non utilizzarla come strumento di pagamento</string>
|
||||
<string name="alert_unsupported_card">Questa carta non è progettata per funzionare con Tangem</string>
|
||||
<string name="chat_bot_name">Tangem Bot</string>
|
||||
<string name="common_accept">Accetta</string>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<string name="eth_gas_required_exceeds_allowance">Недостаточно средств для совершения транзакции. Пожалуйста, пополните свой аккаунт.</string>
|
||||
<string name="generic_error_code">Произошла ошибка. Код: %s.</string>
|
||||
<string name="kaspa_withdrawal_message_warning">Из-за ограничений Kaspa в одну транзакцию может поместиться только %1$d UTXO. Это означает, что вы можете отправить только %2$s или меньше. Вам нужно уменьшить сумму.</string>
|
||||
<string name="no_account_generic">Пополните счет на %1$s+ %2$s, чтобы создать аккаунт</string>
|
||||
<string name="no_account_generic">Чтобы использовать сеть %1$s, вы должны оплатить резерв аккаунта (%2$s %3$s), который блокируется и не используется в вашем балансе.</string>
|
||||
<string name="no_account_polkadot">Аккаунт получателя не активирован. Отправьте %s или более для активации аккаунта.</string>
|
||||
<string name="send_error_dust_amount_format">Минимальная сумма: %s</string>
|
||||
<string name="send_error_dust_change">Сдача слишком мала</string>
|
||||
|
|
|
|||
|
|
@ -7,17 +7,12 @@
|
|||
<string name="alert_app_feedback_sent_title">Отправлено успешно</string>
|
||||
<string name="alert_button_request_support">Обратиться в поддержку</string>
|
||||
<string name="alert_button_send_feedback">Отправить отзыв</string>
|
||||
<string name="alert_card_signed_transactions">Эта карта ранее пополнялась и подписывала транзакции. Выведите средства как можно быстрее, если вы получили эту карту из ненадежного источника. Если это ваша карта, то не о чем беспокоиться.</string>
|
||||
<string name="alert_demo_feature_disabled">Эта функция недоступна в демонстрационном режиме</string>
|
||||
<string name="alert_demo_message">Приложение работает в демонстрационном режиме. Средства на всех счетах ненастоящие.</string>
|
||||
<string name="alert_developer_card">Карта, которую вы отсканировали, является картой разработчика. Не принимайте её в качестве оплаты.</string>
|
||||
<string name="alert_failed_to_send_email_title">Не удалось отправить письмо</string>
|
||||
<string name="alert_failed_to_send_transaction_message">Причина: %s</string>
|
||||
<string name="alert_failed_to_send_transaction_title">Не могу отправить транзакцию</string>
|
||||
<string name="alert_manage_tokens_addresses_message">Внимание! Валюты на разных сетях имеют разные адреса. Убедитесь, что адрес соответствует сети, в которой вы отправляете средства.</string>
|
||||
<string name="alert_manage_tokens_unsupported_curve_message">Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен.</string>
|
||||
<string name="alert_manage_tokens_unsupported_message">Токены в сети Solana не поддерживаются этой картой из-за ограничений прошивки.</string>
|
||||
<string name="alert_signed_hashes_message">Эта карта не является платежным средством. В настоящее время мы не можем сопоставить количество подписей на карте с информацией в блокчейне. Это нормально, но в редких случаях может означать, что предыдущий владелец удерживает подписанную транзакцию от публикации, что является поводом для беспокойства.\nНе принимайте эту карту в качестве физического платежа от кого-то, кому вы не доверяете.\nВо всех остальных отношениях эта карта совершенно безопасна.\nTangem — единственный аппаратный кошелек, предлагающий защиту методом подсчета подписей.</string>
|
||||
<string name="alert_troubleshooting_scan_card_title">У вас возникли трудности со сканированием карты?</string>
|
||||
<string name="alert_unsupported_card">Эта карта не предназначена для работы с этим приложением</string>
|
||||
<string name="app_settings_enable_biometrics_description">Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem</string>
|
||||
|
|
@ -28,11 +23,16 @@
|
|||
<string name="app_settings_saved_access_codes_footer">Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой вместо кода доступа будет запрашиваться биометрическая аутентификация.</string>
|
||||
<string name="app_settings_saved_wallet">Cохранение кошелька</string>
|
||||
<string name="app_settings_saved_wallet_footer">Подключите функцию привязки карты в приложении, а также возможность биометрической аутентификации. Подпись транзакции все так же потребует карту.</string>
|
||||
<string name="app_settings_theme_mode_dark">Тёмная</string>
|
||||
<string name="app_settings_theme_mode_light">Светлая</string>
|
||||
<string name="app_settings_theme_mode_system">Как в системе</string>
|
||||
<string name="app_settings_theme_selector_title">Тема</string>
|
||||
<string name="app_settings_title">Настройки приложения</string>
|
||||
<string name="biometric_lockout_permanent_warning_description">Пожалуйста, отсканируйте карту</string>
|
||||
<string name="biometric_lockout_warning_description">Пожалуйста, попробуйте снова через 30 секунд или отсканируйте карту</string>
|
||||
<string name="biometric_lockout_warning_title">Слишком много попыток</string>
|
||||
<string name="biometric_unavailable_warning">Вы отключили биометрическую аутентификацию на вашем телефоне и не сможете сохранять кошельки в приложении. Для сохранения кошельков, пожалуйста, включите функцию биометрической аутентификации в настройках телефона.</string>
|
||||
<string name="button_start_backup_process">Начать резервное копирование</string>
|
||||
<plurals name="card_label_card_count">
|
||||
<item quantity="one">%d карта</item>
|
||||
<item quantity="few">%d карты</item>
|
||||
|
|
@ -43,6 +43,7 @@
|
|||
<string name="card_settings_access_code_recovery_enabled_description">Использовать эту карту для сброса кода доступа на других картах в этом кошельке</string>
|
||||
<string name="card_settings_access_code_recovery_footer">Отключить возможность сброса кода доступа на этой карте или других картах этого кошелька</string>
|
||||
<string name="card_settings_access_code_recovery_title">Восстановление кода доступа</string>
|
||||
<string name="card_settings_action_sheet_reset">Сбросить</string>
|
||||
<string name="card_settings_action_sheet_title">Вы уверены, что хотите это сделать?</string>
|
||||
<string name="card_settings_change_access_code">Смена кода доступа</string>
|
||||
<string name="card_settings_change_access_code_footer">Код доступа будет изменен только на данной карте</string>
|
||||
|
|
@ -57,6 +58,7 @@
|
|||
<string name="chat_user_actions_title">Пожалуйста, выберите действие</string>
|
||||
<string name="chat_user_rate_agent_title">Пожалуйста, оцените работу агента</string>
|
||||
<string name="common_accept">Принять</string>
|
||||
<string name="common_access_denied">Доступ запрещен</string>
|
||||
<string name="common_add">Добавить</string>
|
||||
<string name="common_apply">Применить</string>
|
||||
<string name="common_approval">Одобрение</string>
|
||||
|
|
@ -65,6 +67,7 @@
|
|||
<string name="common_biometric_authentication">биометрическую аутентификацию</string>
|
||||
<string name="common_biometrics">биометрией</string>
|
||||
<string name="common_buy">Купить</string>
|
||||
<string name="common_buy_currency">Купить %1$s</string>
|
||||
<string name="common_camera_denied_alert_message">Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности.</string>
|
||||
<string name="common_cancel">Отмена</string>
|
||||
<string name="common_close">Закрыть</string>
|
||||
|
|
@ -87,6 +90,7 @@
|
|||
<string name="common_like">Нравится</string>
|
||||
<string name="common_locked">Заблокирован</string>
|
||||
<string name="common_main_network">Основная сеть</string>
|
||||
<string name="common_next">Далее</string>
|
||||
<string name="common_no">Нет</string>
|
||||
<string name="common_no_address">Нет адреса</string>
|
||||
<string name="common_no_data">Нет данных</string>
|
||||
|
|
@ -96,7 +100,6 @@
|
|||
<string name="common_reject">Отклонить</string>
|
||||
<string name="common_reload">Перезагрузить</string>
|
||||
<string name="common_rename">Переименовать</string>
|
||||
<string name="common_reset">Сбросить</string>
|
||||
<string name="common_save_changes">Сохранить изменения</string>
|
||||
<string name="common_search">Искать</string>
|
||||
<string name="common_search_tokens">Поиск токенов</string>
|
||||
|
|
@ -114,10 +117,10 @@
|
|||
<string name="common_success">Успешно</string>
|
||||
<string name="common_swap">Обмен</string>
|
||||
<string name="common_terms_and_conditions">условия участия</string>
|
||||
<string name="common_transaction_failed">Ошибка транзакции</string>
|
||||
<string name="common_transactions">Транзакции</string>
|
||||
<string name="common_transfer">Перевод</string>
|
||||
<string name="common_understand">Я понял</string>
|
||||
<string name="common_unlock_needed">Необходима разблокировка</string>
|
||||
<string name="common_unreachable">Недоступно</string>
|
||||
<string name="common_yes">Да</string>
|
||||
<string name="contract_address_copied_message">Адрес контракта скопирован!</string>
|
||||
|
|
@ -139,7 +142,8 @@
|
|||
<string name="custom_token_network_input_not_selected">Не выбрано</string>
|
||||
<string name="custom_token_network_input_title">Сеть</string>
|
||||
<string name="custom_token_token_symbol_input_placeholder">Например, USDC</string>
|
||||
<string name="custom_token_token_symbol_input_title">Символ токена</string>
|
||||
<string name="custom_token_token_symbol_input_title">Символ</string>
|
||||
<string name="custom_token_token_symbol_input_title_old">Символ токена</string>
|
||||
<string name="custom_token_validation_error_already_added">Этот токен/сеть уже находится в вашем списке</string>
|
||||
<string name="custom_token_validation_error_not_found">Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить</string>
|
||||
<string name="details_chat">Чат</string>
|
||||
|
|
@ -150,11 +154,13 @@
|
|||
<string name="details_manage_security_passcode">Пароль</string>
|
||||
<string name="details_manage_security_passcode_description">Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль.</string>
|
||||
<string name="details_referral_title">Реферальная программа</string>
|
||||
<string name="details_row_description_flip_to_hide">Переверните экран вашего устройства вниз, чтобы быстро скрыть и отобразить балансы</string>
|
||||
<string name="details_row_privacy_policy">Privacy policy</string>
|
||||
<string name="details_row_subtitle_signed_hashes_format">%s хэшей</string>
|
||||
<string name="details_row_title_cid">Номер карты</string>
|
||||
<string name="details_row_title_create_backup">Добавить еще карты</string>
|
||||
<string name="details_row_title_currency">Валюта приложения</string>
|
||||
<string name="details_row_title_flip_to_hide">Скрывать балансы жестом переворота</string>
|
||||
<string name="details_row_title_issuer">Эмитент</string>
|
||||
<string name="details_row_title_send_feedback">Отправить отзыв</string>
|
||||
<string name="details_row_title_signed_hashes">Подписано</string>
|
||||
|
|
@ -187,6 +193,7 @@
|
|||
<string name="initial_message_tap_header">Приложите карту</string>
|
||||
<string name="internal_error_wallet_manager_not_found">Внутренняя ошибка: не удается найти менеджер кошельков</string>
|
||||
<string name="key_invalidated_warning_description">Вы обновили данные биометрии, отсканируйте свою карту для входа</string>
|
||||
<string name="main_empty_tokens_list_message">Чтобы начать отслеживать свои криптоактивы и транзакции, добавьте токены</string>
|
||||
<string name="main_get_bonus_subtitle">Вы успешно прошли все уроки и теперь можете получить 1INCH токены</string>
|
||||
<plurals name="main_learn_subtitle">
|
||||
<item quantity="one">Пройдите 3 урока и получите %d 1INCH токен на свой кошелек</item>
|
||||
|
|
@ -195,21 +202,14 @@
|
|||
<item quantity="other">Пройдите 3 урока и получите %d 1INCH токенов на свой кошелек</item>
|
||||
</plurals>
|
||||
<string name="main_manage_tokens">Управление токенами</string>
|
||||
<string name="main_no_backup_warning_subtitle">Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру</string>
|
||||
<string name="main_no_backup_warning_title">Бэкап кошелька не был произведен</string>
|
||||
<string name="main_page_balance">Баланс</string>
|
||||
<string name="main_processing_full_amount">В сумме учтены не все монеты</string>
|
||||
<string name="main_promotion_credited">1INCH токены будут зачислены на адрес вашего кошелька в сети %s в течение 48 часов</string>
|
||||
<string name="main_scan_card_warning_view_subtitle">Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту</string>
|
||||
<string name="main_scan_card_warning_view_title">Отсканируйте карту</string>
|
||||
<string name="main_tokens">Токены</string>
|
||||
<plurals name="main_warning_missing_derivation_description">
|
||||
<item quantity="one">Вам надо сгенерировать адрес для %d новой сети, используя вашу карту</item>
|
||||
<item quantity="few">Вам надо сгенерировать адреса для %d новых сетей, используя вашу карту</item>
|
||||
<item quantity="many">Вам надо сгенерировать адреса для %d новых сетей, используя вашу карту</item>
|
||||
<item quantity="other">Вам надо сгенерировать адреса для %d новых сетей, используя вашу карту</item>
|
||||
</plurals>
|
||||
<string name="main_warning_missing_derivation_title">Некоторые адреса отсутствуют</string>
|
||||
<string name="manage_tokens_add">Добавить</string>
|
||||
<string name="manage_tokens_edit">Изменить</string>
|
||||
<string name="onboarding_access_code_feature_1_description">Вам необходимо установить единый код доступа для защиты всех ваших карт</string>
|
||||
<string name="onboarding_access_code_feature_1_title">Защита</string>
|
||||
<string name="onboarding_access_code_feature_2_description">Позже вы сможете установить индивидуальный код доступа для каждой карты</string>
|
||||
|
|
@ -298,12 +298,13 @@
|
|||
<string name="onboarding_wallet_info_title_fourth">Восстановление кода доступа</string>
|
||||
<string name="onboarding_wallet_info_title_second">Идентичные карты</string>
|
||||
<string name="onboarding_wallet_info_title_third">Код доступа</string>
|
||||
<string name="organize_tokens_group">Группировка</string>
|
||||
<string name="organize_tokens_group">Группы</string>
|
||||
<string name="organize_tokens_sort_by_balance">По балансу</string>
|
||||
<string name="organize_tokens_title">Сортировка токенов</string>
|
||||
<string name="organize_tokens_ungroup">Разгруппировать</string>
|
||||
<string name="organize_tokens_ungroup">Список</string>
|
||||
<string name="receive_bottom_sheet_title">%1$s %2$s адрес в сети %3$s</string>
|
||||
<string name="receive_bottom_sheet_warning_message">%1$s (%2$s) в сети %3$s</string>
|
||||
<string name="receive_bottom_sheet_warning_message_full">Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств.</string>
|
||||
<string name="referral_button_participate">Участвовать</string>
|
||||
<string name="referral_error_failed_to_load_info">Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже.</string>
|
||||
<string name="referral_error_failed_to_load_info_with_reason">Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже.</string>
|
||||
|
|
@ -341,7 +342,8 @@
|
|||
</plurals>
|
||||
<string name="registration_task_alert_message">Пожалуйста, удерживайте карту до завершения операции</string>
|
||||
<string name="reset_card_to_factory_button_title">Сбросить карту</string>
|
||||
<string name="reset_card_to_factory_warning_message">Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку</string>
|
||||
<string name="reset_card_to_factory_condition_1">Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку</string>
|
||||
<string name="reset_card_to_factory_condition_2">Я понимаю, что не смогу этой картой восстановить пароль на остальных картах этого кошелька, если я его забуду</string>
|
||||
<string name="reset_card_with_backup_to_factory_message">Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать данную карту для восстановления кода доступа.</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек.</string>
|
||||
<string name="russian_bank_card_warning_subtitle">У вас есть карта банка другой страны, а также вид на жительство или регистрация вне РФ?</string>
|
||||
|
|
@ -386,10 +388,9 @@
|
|||
<string name="shop_other_payment_methods">Другие способы оплаты</string>
|
||||
<string name="shop_pre_order_now">Сделать предзаказ</string>
|
||||
<string name="shop_total">Итого</string>
|
||||
<string name="solana_rent_warning">Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату.</string>
|
||||
<string name="story_awe_description">Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте.</string>
|
||||
<string name="story_awe_title">Революционный аппаратный кошелек</string>
|
||||
<string name="story_backup_description">До **трех карт** с одним кошельком</string>
|
||||
<string name="story_backup_description">До трех карт с одним кошельком</string>
|
||||
<string name="story_backup_description_1">До</string>
|
||||
<string name="story_backup_description_2_bold">трех карт</string>
|
||||
<string name="story_backup_description_3">с одним кошельком</string>
|
||||
|
|
@ -400,16 +401,9 @@
|
|||
<string name="story_finish_title">Кошелек для каждого</string>
|
||||
<string name="story_learn_description">Пройдите 3 урока, получите скидку на покупку Tangem Wallet и 1INCH токены</string>
|
||||
<string name="story_learn_learn">Пройти обучение</string>
|
||||
<string name="story_meet_borrow">Занимайте</string>
|
||||
<string name="story_meet_buy">Покупайте</string>
|
||||
<string name="story_meet_exchange">Обменивайте</string>
|
||||
<string name="story_meet_lend">Вкладывайте</string>
|
||||
<string name="story_meet_pay">Расплачивайтесь</string>
|
||||
<string name="story_meet_send">Отправляйте</string>
|
||||
<string name="story_meet_store">Храните</string>
|
||||
<string name="story_meet_title">Встречайте\nTangem</string>
|
||||
<string name="story_meet_title">Встречайте Tangem</string>
|
||||
<string name="story_web3_description">Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах</string>
|
||||
<string name="story_web3_title">Поддержка DeFi</string>
|
||||
<string name="story_web3_title">Поддержка Web 3.0</string>
|
||||
<string name="swapping_approve_information_text">Подтверждения считаются отраслевым стандартом для всех децентрализованных бирж и защищают ваш кошелек от доступа со стороны смарт-контракта без вашего разрешения. По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту 1inch разрешение тратить ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете обменять свой токен.</string>
|
||||
<string name="swapping_approve_information_title">Подтвердить</string>
|
||||
<string name="swapping_error_wrapper">Ошибка: %s</string>
|
||||
|
|
@ -450,8 +444,6 @@
|
|||
<string name="token_details_hide_alert_message">Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами.</string>
|
||||
<string name="token_details_hide_alert_title">Скрыть %s</string>
|
||||
<string name="token_details_hide_token">Скрыть токен</string>
|
||||
<string name="token_details_send_blocked_fee_format">%1$s — это токен в сети %2$s. Чтобы отправить транзакцию %3$s, необходимо пополнить баланс %4$s (%5$s) для оплаты комиссии сети.</string>
|
||||
<string name="token_details_send_blocked_tx_format">Пожалуйста, дождитесь завершения транзакции %s, чтобы иметь возможность отправить средства</string>
|
||||
<string name="token_details_token_type_subtitle">%1$s токен в сети %%image%% %2$s</string>
|
||||
<string name="token_details_unable_hide_alert_message">Токен %1$s является основной валютой в сети %2$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети.</string>
|
||||
<string name="token_details_unable_hide_alert_title">Невозможно скрыть %s</string>
|
||||
|
|
@ -476,7 +468,6 @@
|
|||
<string name="twins_recreate_warning">Это действие необратимо. У вас не будет доступа к старому кошельку.</string>
|
||||
<string name="twins_scan_twin_with_number">Приложите twin-карту с номером %s и не убирайте до окончания операции</string>
|
||||
<string name="unlock_wallet_description_full">Используйте %s или отсканируйте карту, чтобы получить доступ к своему кошельку</string>
|
||||
<string name="unlock_wallet_description_short">Используйте %s или отсканируйте карту</string>
|
||||
<string name="user_wallet_list_add_button">Добавить новый кошелек</string>
|
||||
<string name="user_wallet_list_delete_prompt">Вы уверены, что хотите удалить этот кошелек?</string>
|
||||
<string name="user_wallet_list_editing_count">%d выбрано</string>
|
||||
|
|
@ -487,9 +478,9 @@
|
|||
<string name="user_wallet_list_rename_popup_title">Переименование кошелька</string>
|
||||
<string name="user_wallet_list_single_header">Одновалютные</string>
|
||||
<string name="user_wallet_list_title">Мои кошельки</string>
|
||||
<string name="user_wallet_list_unlock_all">Разблокировать все с %s</string>
|
||||
<string name="user_wallet_list_unlock_all">Разблокировать все</string>
|
||||
<string name="user_wallet_list_unlock_all_with">Разблокировать все с %s</string>
|
||||
<string name="wallet_address_button_explore">История транзакций</string>
|
||||
<string name="wallet_balance_blockchain_unreachable">Сеть недоступна</string>
|
||||
<string name="wallet_balance_blockchain_unreachable_try_later">Блокчейн недоступен. Попробуйте позже.</string>
|
||||
<string name="wallet_balance_loading">Баланс загружается…</string>
|
||||
<string name="wallet_balance_missing_derivation">Отсканируйте карту</string>
|
||||
|
|
@ -520,6 +511,7 @@
|
|||
<string name="wallet_connect_network_not_found_format">Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново.</string>
|
||||
<string name="wallet_connect_no_sessions_message">Нет открытых сессий WalletConnect</string>
|
||||
<string name="wallet_connect_no_sessions_title">Упс. Нет сессий.</string>
|
||||
<string name="wallet_connect_pairing_error">Не удалось создать пару WalletConnect: %1$s</string>
|
||||
<string name="wallet_connect_paste_from_clipboard">Вставить из буфера обмена</string>
|
||||
<string name="wallet_connect_personal_sign_message">Сообщение для %1$s:\n%2$s</string>
|
||||
<string name="wallet_connect_request_session_start">Запрос на открытие сессии для\n%1$s\n\nСЕТЬ: %2$s\n\nURL: %3$s</string>
|
||||
|
|
@ -550,19 +542,50 @@
|
|||
<string name="wallet_pending_tx_sending">Отправка</string>
|
||||
<string name="wallet_pending_tx_sending_address_format">к %s</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="warning_button_can_be_better">Можно лучше</string>
|
||||
<string name="warning_access_denied_message">Используйте %s или отсканируйте карту, чтобы разблокировать доступ к вашему кошельку</string>
|
||||
<string name="warning_button_could_be_better">Можно лучше</string>
|
||||
<string name="warning_button_learn_more">Узнать больше</string>
|
||||
<string name="warning_button_like_it">Нравится</string>
|
||||
<string name="warning_button_ok">Понятно!</string>
|
||||
<string name="warning_button_really_cool">Очень круто!</string>
|
||||
<string name="warning_existential_deposit_message">Сеть %1$s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %2$s, он будет деактивирован, а все оставшиеся средства будут уничтожены.</string>
|
||||
<string name="warning_failed_to_verify_card_message">Эта карта может быть производственным образцом или подделкой</string>
|
||||
<string name="warning_failed_to_verify_card_title">Проверка подлинности не удалась</string>
|
||||
<string name="warning_important_security_info">Важная информация о безопасности %s</string>
|
||||
<string name="warning_low_signatures_format">На этой карте осталось только %s подписей. Вы должны вывести все свои средства.</string>
|
||||
<string name="warning_rate_app_message">Как вам Tangem?</string>
|
||||
<string name="warning_rate_app_title">Один вопрос</string>
|
||||
<string name="warning_signed_tx_previously">Эта карта подписывала транзакции в прошлом</string>
|
||||
<string name="warning_testnet_card_message">Это тестовая карта. Не принимайте её в качестве оплаты. Эта карта должна использоваться только в целях тестирования и разработки.</string>
|
||||
<string name="warning_demo_mode_message">Вы находитесь в режиме демо</string>
|
||||
<string name="warning_demo_mode_title">Демо режим включен</string>
|
||||
<string name="warning_developer_card_message">Отсканированная вами карта является картой разработчика. Не используйте ее для создания своего кошелька.</string>
|
||||
<string name="warning_developer_card_title">Не для пользователя!</string>
|
||||
<string name="warning_existential_deposit_message">Cеть %1$s использует концепцию экзистенциального депозита. Если баланс вашего счета будет ниже %2$s, то он будет деактивирован, а средства на счете уничтожены.</string>
|
||||
<string name="warning_existential_deposit_title">Для работы с сетью необходим депозит</string>
|
||||
<string name="warning_failed_to_verify_card_message">Возможно, данная карта - образец или подделка</string>
|
||||
<string name="warning_failed_to_verify_card_title">Ошибка проверки подлинности</string>
|
||||
<string name="warning_low_signatures_message">На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства.</string>
|
||||
<string name="warning_low_signatures_title">Малое количество подписей</string>
|
||||
<string name="warning_manage_tokens_legacy_derivation_message">Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети.</string>
|
||||
<plurals name="warning_missing_derivation_message">
|
||||
<item quantity="one">Используйте вашу карту, чтобы сгенерировать адрес для %d новой сети</item>
|
||||
<item quantity="few">Используйте вашу карту, чтобы сгенерировать адреса для %d новых сетей</item>
|
||||
<item quantity="many">Используйте вашу карту, чтобы сгенерировать адреса для %d новых сетей</item>
|
||||
<item quantity="other">Используйте вашу карту, чтобы сгенерировать адреса для %d новых сетей</item>
|
||||
</plurals>
|
||||
<string name="warning_missing_derivation_title">Некоторые адреса отсутствуют</string>
|
||||
<string name="warning_network_unreachable_message">В данный момент сеть недоступна. Пожалуйста, попробуйте позже.</string>
|
||||
<string name="warning_network_unreachable_title">Сеть недоступна</string>
|
||||
<string name="warning_no_account_title">Пополните ваш кошелек</string>
|
||||
<string name="warning_no_backup_message">Ваш кошелек не имеет резервной копии. Проведите эту процедуру сейчас, чтобы защитить ваши активы.</string>
|
||||
<string name="warning_no_backup_title">Резервная копия отсутствует</string>
|
||||
<string name="warning_number_of_signed_hashes_incorrect_message">Эта карта ранее использовалась для подписи транзакций. Если она получена от ненадежного источника, рассмотрите возможность вывода своих средств. Если это ваша карта, дополнительных действий не требуется.</string>
|
||||
<string name="warning_number_of_signed_hashes_incorrect_title">Карта уже подписывала транзакции</string>
|
||||
<string name="warning_old_device_old_card_title">Устройство несовместимо</string>
|
||||
<string name="warning_rate_app_message">Ваш отзыв мотивирует нас сделать кошелек Tangem еще лучше</string>
|
||||
<string name="warning_rate_app_title">Нравится Tangem?</string>
|
||||
<string name="warning_rent_fee_title">Необходима плата за аренду сети</string>
|
||||
<string name="warning_send_blocked_funds_for_fee_message">%1$s - это токен в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести депозит в размере %4$s (%5$s), чтобы покрыть комиссию сети.</string>
|
||||
<string name="warning_send_blocked_funds_for_fee_title">Недостаточно %1$s для оплаты комиссии сети</string>
|
||||
<string name="warning_send_blocked_pending_transactions_message">Отправка средств станет доступной после завершения транзакции %s</string>
|
||||
<string name="warning_send_blocked_pending_transactions_title">Транзакция в обработке</string>
|
||||
<string name="warning_solana_rent_fee_message">Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату.</string>
|
||||
<string name="warning_some_networks_unreachable_message">Некоторые сети в настоящее время недоступны. Пожалуйста, повторите попытку позже.</string>
|
||||
<string name="warning_some_networks_unreachable_title">Некоторые сети недоступны</string>
|
||||
<string name="warning_testnet_card_message">Это Testnet карта. Он не может обрабатывать транзакции и используется только в целях тестирования и разработки.</string>
|
||||
<string name="warning_testnet_card_title">Только для целей тестирования</string>
|
||||
<string name="welcome_interrupted_backup_alert_discard">Отказаться</string>
|
||||
<string name="welcome_interrupted_backup_alert_message">Вы не закончили резервное копирование. Хотите продолжить?</string>
|
||||
<string name="welcome_interrupted_backup_alert_resume">Да, возобновить</string>
|
||||
|
|
@ -573,5 +596,5 @@
|
|||
<string name="welcome_unlock">Войти с %s</string>
|
||||
<string name="welcome_unlock_card">Сканировать карту</string>
|
||||
<string name="welcome_unlock_description">Используйте %s или отсканируйте карту для входа в приложение</string>
|
||||
<string name="welcome_unlock_title">С возвращением!</string>
|
||||
<string name="welcome_unlock_title">C возвращением!</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -7,16 +7,11 @@
|
|||
<string name="alert_app_feedback_sent_title">成功送出</string>
|
||||
<string name="alert_button_request_support">請求支持</string>
|
||||
<string name="alert_button_send_feedback">發送反饋</string>
|
||||
<string name="alert_card_signed_transactions">此卡過去已經充值並簽署過交易。如果您從不受信任的來源收到此卡,請考慮立即提取所有資金。如果是您的卡,則無需擔心</string>
|
||||
<string name="alert_demo_feature_disabled">此功能不在展示模式中提供</string>
|
||||
<string name="alert_demo_message">您正在展示模式。所有資產皆不是真的</string>
|
||||
<string name="alert_developer_card">您掃描的卡是開發卡。不要用它作為付款方式</string>
|
||||
<string name="alert_failed_to_send_email_title">發送電子郵件失敗</string>
|
||||
<string name="alert_failed_to_send_transaction_message">原因:%s</string>
|
||||
<string name="alert_failed_to_send_transaction_title">無法發送交易</string>
|
||||
<string name="alert_manage_tokens_addresses_message">請注意代幣在不同的網路有不同的地址。請再次檢查正確的地址</string>
|
||||
<string name="alert_manage_tokens_unsupported_message">此卡不支持Solana網路上的代幣因為韌體限制</string>
|
||||
<string name="alert_signed_hashes_message">這張卡不是不記名票據。我們目前無法將卡上的簽名計數與區塊鏈上的信息相匹配。這很正常,但在極少數情況下可能意味著以前的持有人正在阻止離線簽名,這是安全問題。不要接受此卡作為您不信任的人的實物付款。它在所有其他方面都非常安全。Tangem 是唯一提供簽名計數保護的硬件錢包。</string>
|
||||
<string name="alert_troubleshooting_scan_card_title">有困難在掃描卡上嗎?</string>
|
||||
<string name="alert_unsupported_card">此卡不適用於此app</string>
|
||||
<string name="app_settings_enable_biometrics_description">轉到設置以在 Tangem App 中啟用生物識別身份驗證</string>
|
||||
|
|
@ -38,6 +33,7 @@
|
|||
<string name="card_settings_access_code_recovery_enabled_description">允許您使用此卡重置此錢包中其他卡上的訪問密碼</string>
|
||||
<string name="card_settings_access_code_recovery_footer">禁用重置此卡或此錢包中其他卡上的訪問密碼的功能</string>
|
||||
<string name="card_settings_access_code_recovery_title">恢復訪問密碼</string>
|
||||
<string name="card_settings_action_sheet_reset">重置</string>
|
||||
<string name="card_settings_action_sheet_title">您確定要這麼做嗎?</string>
|
||||
<string name="card_settings_change_access_code">更改訪問密碼</string>
|
||||
<string name="card_settings_change_access_code_footer">訪問密碼將僅在此卡上更改</string>
|
||||
|
|
@ -80,7 +76,6 @@
|
|||
<string name="common_origin_card">主卡片</string>
|
||||
<string name="common_reject">拒絕</string>
|
||||
<string name="common_rename">重新命名</string>
|
||||
<string name="common_reset">重置</string>
|
||||
<string name="common_save_changes">保存設置</string>
|
||||
<string name="common_search">搜索</string>
|
||||
<string name="common_search_tokens">搜尋代幣</string>
|
||||
|
|
@ -117,7 +112,7 @@
|
|||
<string name="custom_token_network_input_not_selected">未選擇</string>
|
||||
<string name="custom_token_network_input_title">網路</string>
|
||||
<string name="custom_token_token_symbol_input_placeholder">如 USDC</string>
|
||||
<string name="custom_token_token_symbol_input_title">代幣符號</string>
|
||||
<string name="custom_token_token_symbol_input_title_old">代幣符號</string>
|
||||
<string name="custom_token_validation_error_already_added">此代幣/網路已被加入</string>
|
||||
<string name="custom_token_validation_error_not_found">請注意代幣可以被任何人創造。小心詐騙</string>
|
||||
<string name="details_chat">交談</string>
|
||||
|
|
@ -153,7 +148,6 @@
|
|||
<string name="feedback_subject_support">反饋</string>
|
||||
<string name="feedback_subject_support_tangem">Tangem反饋</string>
|
||||
<string name="feedback_subject_tx_failed">無法發送交易</string>
|
||||
<string name="home_button_order">訂購</string>
|
||||
<string name="home_button_scan">掃描卡片</string>
|
||||
<string name="initial_message_change_access_code_body">要更改訪問密碼,請完全按照上圖所示連接手機和卡片</string>
|
||||
<string name="initial_message_change_passcode_body">要更改密碼,請完全按照上圖所示連接手機和卡</string>
|
||||
|
|
@ -166,8 +160,6 @@
|
|||
<string name="internal_error_wallet_manager_not_found">內部錯誤:找不到錢包管理器</string>
|
||||
<string name="key_invalidated_warning_description">您已更新生物識別登入,掃描您的卡進入</string>
|
||||
<string name="main_manage_tokens">管理代幣</string>
|
||||
<string name="main_no_backup_warning_subtitle">為了保護您的資產,我們建議您執行此程序</string>
|
||||
<string name="main_no_backup_warning_title">您的錢包尚未備份</string>
|
||||
<string name="main_page_balance">總餘額</string>
|
||||
<string name="main_processing_full_amount">該金額不包括您的部分資金</string>
|
||||
<string name="main_scan_card_warning_view_subtitle">要訪問所有的網路您需要掃描卡片</string>
|
||||
|
|
@ -277,10 +269,9 @@
|
|||
</plurals>
|
||||
<string name="registration_task_alert_message">請持有卡片直至操作完成</string>
|
||||
<string name="reset_card_to_factory_button_title">重置卡片</string>
|
||||
<string name="reset_card_to_factory_warning_message">我了解執行此操作後,我將無法再訪問當前錢包</string>
|
||||
<string name="reset_card_to_factory_condition_1">我了解執行此操作後,我將無法再訪問當前錢包</string>
|
||||
<string name="reset_card_with_backup_to_factory_message">恢復原廠設置將從所選卡中完全刪除錢包。您將無法恢復當前錢包或使用卡恢復訪問密碼</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">恢復原廠設置將從所選卡中完全刪除錢包並將其從應用程序中刪除。您將無法恢復當前錢包</string>
|
||||
<string name="russian_bank_card_warning_subtitle">您有其他國家的銀行卡或銀聯卡嗎?</string>
|
||||
<string name="russian_bank_card_warning_title">目前不接受俄羅斯銀行卡</string>
|
||||
<string name="save_user_wallet_agreement_access_description">登錄應用程序並在不掃描卡片的情況下檢查您的資產</string>
|
||||
<string name="save_user_wallet_agreement_access_title">訪問應用程序</string>
|
||||
|
|
@ -321,10 +312,9 @@
|
|||
<string name="shop_one_wallet">Tangem Wallet</string>
|
||||
<string name="shop_other_payment_methods">其他付款方式</string>
|
||||
<string name="shop_total">總計</string>
|
||||
<string name="solana_rent_warning">Solana 網絡每 2 天收取 %1$s 的費用。無法付此費用的帳戶將從網絡中清除。向您的帳戶存入超過 %2$s 即可免費使用</string>
|
||||
<string name="story_awe_description">安全地存儲您的加密貨幣,同時將私鑰保存在您的卡中</string>
|
||||
<string name="story_awe_title">創新式的硬體錢包</string>
|
||||
<string name="story_backup_description">最多 **3張實體卡片** 到一個錢包</string>
|
||||
<string name="story_backup_description">最多 3張實體卡片 到一個錢包</string>
|
||||
<string name="story_backup_description_1">最多</string>
|
||||
<string name="story_backup_description_2_bold">3張實體卡片</string>
|
||||
<string name="story_backup_description_3">到一個錢包</string>
|
||||
|
|
@ -333,16 +323,9 @@
|
|||
<string name="story_currencies_title">千種虛擬貨幣</string>
|
||||
<string name="story_finish_description">隨時隨地使用它,不需要接線或電池,只需要你的卡片和手機</string>
|
||||
<string name="story_finish_title">適合每個人的冷錢包</string>
|
||||
<string name="story_meet_borrow">借入</string>
|
||||
<string name="story_meet_buy">購買</string>
|
||||
<string name="story_meet_exchange">交易</string>
|
||||
<string name="story_meet_lend">借出</string>
|
||||
<string name="story_meet_pay">付款</string>
|
||||
<string name="story_meet_send">發送</string>
|
||||
<string name="story_meet_store">保存</string>
|
||||
<string name="story_meet_title">認識Tangem</string>
|
||||
<string name="story_web3_description">交易、購買NFT、自由穿梭於100+去中心化服務</string>
|
||||
<string name="story_web3_title">兼容DeFi</string>
|
||||
<string name="story_web3_title">兼容Web3.0</string>
|
||||
<string name="swapping_approve_information_text">批准被視為所有去中心化交易所的行業標準,並保護您的錢包在未經您許可的情況下不被智能合約訪問。按照設計,智能合約無法訪問您的代幣,除非您從您的終端批准訪問。通過“解鎖”您的代幣,您將獲得 1inch 智能合約使用您的資產的權限。網絡的礦工將獲得Gas Fee(由您支付)作為補償,以在區塊鏈上記錄此操作。一旦獲得許可,您就可以交易您的代幣。</string>
|
||||
<string name="swapping_approve_information_title">批准</string>
|
||||
<string name="swapping_error_wrapper">錯誤: %s</string>
|
||||
|
|
@ -376,8 +359,6 @@
|
|||
<string name="token_details_hide_alert_message">您即將在主屏幕上隱藏此代幣。您可以隨時通過管理代幣頁面將其添加回來。</string>
|
||||
<string name="token_details_hide_alert_title">隱藏 %s</string>
|
||||
<string name="token_details_hide_token">隱藏代幣</string>
|
||||
<string name="token_details_send_blocked_fee_format">%1$s 是 %2$s 網絡中的代幣, 要進行 %3$s 交易,您需要存入一些 %4$s (%5$s) 以支付網絡費用</string>
|
||||
<string name="token_details_send_blocked_tx_format">請等待 %s 交易完成才能發送資金</string>
|
||||
<string name="token_details_unable_hide_alert_message">%1$s 代幣是 %2$s 網絡上的主要貨幣,只要列表中還有該網絡上的其他代幣,它就無法被隱藏。</string>
|
||||
<string name="token_details_unable_hide_alert_title">無法隱藏 %s</string>
|
||||
<string name="token_item_no_rate">無費用</string>
|
||||
|
|
@ -405,9 +386,8 @@
|
|||
<string name="user_wallet_list_rename_popup_title">重新命名錢包</string>
|
||||
<string name="user_wallet_list_single_header">單一幣種</string>
|
||||
<string name="user_wallet_list_title">我的錢包</string>
|
||||
<string name="user_wallet_list_unlock_all">用 %s 解鎖全部</string>
|
||||
<string name="user_wallet_list_unlock_all_with">用 %s 解鎖全部</string>
|
||||
<string name="wallet_address_button_explore">交易記錄</string>
|
||||
<string name="wallet_balance_blockchain_unreachable">網路無法使用</string>
|
||||
<string name="wallet_balance_blockchain_unreachable_try_later">區塊鍊無法使用。稍後再試</string>
|
||||
<string name="wallet_balance_loading">餘額加載中</string>
|
||||
<string name="wallet_balance_missing_derivation">掃描卡片</string>
|
||||
|
|
@ -458,6 +438,7 @@
|
|||
<string name="wallet_error_no_account">帳號尚未被創造</string>
|
||||
<string name="wallet_error_unsupported_blockchain">不支持此卡</string>
|
||||
<string name="wallet_error_unsupported_blockchain_subtitle">您的 Tangem 卡是為與不同的應用程序一起工作而設計的。請查看卡片上的名稱和說明,並安裝正確的應用程序</string>
|
||||
<string name="wallet_marketplace_block_title">%s市場價格</string>
|
||||
<string name="wallet_notification_address_copied">地址已復製到剪貼板</string>
|
||||
<string name="wallet_notification_no_internet">無網路</string>
|
||||
<string name="wallet_pending_tx_receiving">接收中</string>
|
||||
|
|
@ -465,19 +446,14 @@
|
|||
<string name="wallet_pending_tx_sending">發送中</string>
|
||||
<string name="wallet_pending_tx_sending_address_format">至%s</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="warning_button_can_be_better">可以更好</string>
|
||||
<string name="warning_button_could_be_better">可以更好</string>
|
||||
<string name="warning_button_learn_more">了解更多</string>
|
||||
<string name="warning_button_ok">了解</string>
|
||||
<string name="warning_button_really_cool">真的很酷!</string>
|
||||
<string name="warning_existential_deposit_message">%1$s 網絡有一個 Existential Deposit 的概念。如果您的帳戶低於 %2$s,它將被停用,所有剩餘資金將被銷毀</string>
|
||||
<string name="warning_failed_to_verify_card_message">此卡可能是生產樣本或偽造品</string>
|
||||
<string name="warning_failed_to_verify_card_title">認證檢查失敗</string>
|
||||
<string name="warning_important_security_info">重要安全信息 %s</string>
|
||||
<string name="warning_low_signatures_format">此卡上只有 %s 個簽名可用。您必須提取所有資金</string>
|
||||
<string name="warning_rate_app_message">你喜歡 Tangem 嗎?</string>
|
||||
<string name="warning_rate_app_title">一個問題</string>
|
||||
<string name="warning_signed_tx_previously">此卡過去曾簽署過交易</string>
|
||||
<string name="warning_testnet_card_message">這是一張測試網卡。不要接受它作為付款。 此卡只能用於測試和開發目的</string>
|
||||
<string name="warning_network_unreachable_title">網路無法使用</string>
|
||||
<string name="warning_solana_rent_fee_message">Solana 網絡每 2 天收取 %1$s 的費用。無法付此費用的帳戶將從網絡中清除。向您的帳戶存入超過 %2$s 即可免費使用</string>
|
||||
<string name="welcome_interrupted_backup_alert_discard">捨棄</string>
|
||||
<string name="welcome_interrupted_backup_alert_message">您有一個備份中斷了,您想繼續嗎?</string>
|
||||
<string name="welcome_interrupted_backup_alert_resume">是的,恢復</string>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<string name="eth_gas_required_exceeds_allowance">Not enough funds for the transaction. Please top up your account.</string>
|
||||
<string name="generic_error_code">An error occurred. Code: %s.</string>
|
||||
<string name="kaspa_withdrawal_message_warning">Due to Kaspa limitations only %1$d UTXOs can fit in a single transaction. This means you can only send %2$s or less. You need to reduce the amount.</string>
|
||||
<string name="no_account_generic">Load %1$s+ %2$s to create account</string>
|
||||
<string name="no_account_generic">To use the %1$s network, you must pay the account reserve (%2$s %3$s), which locks up and hides that amount indefinitely.</string>
|
||||
<string name="no_account_polkadot">Destination account is not active. Send %s or more to activate the account.</string>
|
||||
<string name="send_error_dust_amount_format">Minimum amount is %s</string>
|
||||
<string name="send_error_dust_change">Change is too small</string>
|
||||
|
|
|
|||
|
|
@ -7,17 +7,12 @@
|
|||
<string name="alert_app_feedback_sent_title">Sent successfully</string>
|
||||
<string name="alert_button_request_support">Request support</string>
|
||||
<string name="alert_button_send_feedback">Send feedback</string>
|
||||
<string name="alert_card_signed_transactions">This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source. If it\'s your card, there is nothing to worry about.</string>
|
||||
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode</string>
|
||||
<string name="alert_demo_message">You are currently running in Demo mode. All funds are not real.</string>
|
||||
<string name="alert_developer_card">The card you scanned is a development card. Don\'t accept it as a payment.</string>
|
||||
<string name="alert_failed_to_send_email_title">Failed to send the email</string>
|
||||
<string name="alert_failed_to_send_transaction_message">Reason: %s</string>
|
||||
<string name="alert_failed_to_send_transaction_title">Can\'t send a transaction</string>
|
||||
<string name="alert_manage_tokens_addresses_message">Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds.</string>
|
||||
<string name="alert_manage_tokens_unsupported_curve_message">To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset.</string>
|
||||
<string name="alert_manage_tokens_unsupported_message">Tokens in Solana network are not supported by this card due to firmware limitation.</string>
|
||||
<string name="alert_signed_hashes_message">This card is not a bearer note. We can\'t currently match the signature count on the card with the information on the blockchain. This is normal but in rare cases can mean a previous holder is holding back an offline signature, which is a security concern.\nDo not accept this card as physical payment from someone you don\'t trust.\nIt\'s perfectly safe in all other respects.\nTangem is the only hardware wallet to offer signature count protection.</string>
|
||||
<string name="alert_troubleshooting_scan_card_title">Are you having difficulty scanning your card?</string>
|
||||
<string name="alert_unsupported_card">This card is not designed to work with this app</string>
|
||||
<string name="app_settings_enable_biometrics_description">Go to settings to enable biometric authentication in the Tangem App</string>
|
||||
|
|
@ -28,11 +23,16 @@
|
|||
<string name="app_settings_saved_access_codes_footer">Biometric authentication will be requested instead of the access code for interactions with your card.</string>
|
||||
<string name="app_settings_saved_wallet">Keep the wallet in the app</string>
|
||||
<string name="app_settings_saved_wallet_footer">Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card.</string>
|
||||
<string name="app_settings_theme_mode_dark">Dark</string>
|
||||
<string name="app_settings_theme_mode_light">Light</string>
|
||||
<string name="app_settings_theme_mode_system">System default</string>
|
||||
<string name="app_settings_theme_selector_title">Theme</string>
|
||||
<string name="app_settings_title">App Settings</string>
|
||||
<string name="biometric_lockout_permanent_warning_description">Please scan the card</string>
|
||||
<string name="biometric_lockout_warning_description">Please try again in 30 seconds or scan the card</string>
|
||||
<string name="biometric_lockout_warning_title">Too many attempts</string>
|
||||
<string name="biometric_unavailable_warning">You have disabled biometric authentication on your phone and will not be able to save wallets in the app. To save wallets, please enable the biometric authentication function in your phone settings.</string>
|
||||
<string name="button_start_backup_process">Start backup process</string>
|
||||
<plurals name="card_label_card_count">
|
||||
<item quantity="one">%d card</item>
|
||||
<item quantity="other">%d cards</item>
|
||||
|
|
@ -41,6 +41,7 @@
|
|||
<string name="card_settings_access_code_recovery_enabled_description">Allows you to use this card to reset access code on other cards in this wallet</string>
|
||||
<string name="card_settings_access_code_recovery_footer">Disable the ability to reset the access code on this card or other cards in this wallet</string>
|
||||
<string name="card_settings_access_code_recovery_title">Access code recovery</string>
|
||||
<string name="card_settings_action_sheet_reset">Reset</string>
|
||||
<string name="card_settings_action_sheet_title">Are you sure you want to do this?</string>
|
||||
<string name="card_settings_change_access_code">Change Access Code</string>
|
||||
<string name="card_settings_change_access_code_footer">Access code will be changed on this card only</string>
|
||||
|
|
@ -55,6 +56,7 @@
|
|||
<string name="chat_user_actions_title">Please select an action</string>
|
||||
<string name="chat_user_rate_agent_title">Please, rate the work of the agent</string>
|
||||
<string name="common_accept">Accept</string>
|
||||
<string name="common_access_denied">Access denied</string>
|
||||
<string name="common_add">Add</string>
|
||||
<string name="common_apply">Apply</string>
|
||||
<string name="common_approval">Approval</string>
|
||||
|
|
@ -63,6 +65,7 @@
|
|||
<string name="common_biometric_authentication">biometric authentication</string>
|
||||
<string name="common_biometrics">biometrics</string>
|
||||
<string name="common_buy">Buy</string>
|
||||
<string name="common_buy_currency">Buy %1$s</string>
|
||||
<string name="common_camera_denied_alert_message">You have not given access to your camera, please adjust your privacy settings</string>
|
||||
<string name="common_cancel">Cancel</string>
|
||||
<string name="common_close">Close</string>
|
||||
|
|
@ -86,6 +89,7 @@
|
|||
<string name="common_like">Like</string>
|
||||
<string name="common_locked">Locked</string>
|
||||
<string name="common_main_network">Main network</string>
|
||||
<string name="common_next">Next</string>
|
||||
<string name="common_no">No</string>
|
||||
<string name="common_no_address">No address</string>
|
||||
<string name="common_no_data">No data</string>
|
||||
|
|
@ -95,7 +99,6 @@
|
|||
<string name="common_reject">Reject</string>
|
||||
<string name="common_reload">Reload</string>
|
||||
<string name="common_rename">Rename</string>
|
||||
<string name="common_reset">Reset</string>
|
||||
<string name="common_save_changes">Save changes</string>
|
||||
<string name="common_search">Search</string>
|
||||
<string name="common_search_tokens">Search tokens</string>
|
||||
|
|
@ -113,10 +116,10 @@
|
|||
<string name="common_success">Success</string>
|
||||
<string name="common_swap">Swap</string>
|
||||
<string name="common_terms_and_conditions">terms and conditions</string>
|
||||
<string name="common_transaction_failed">Transaction failed</string>
|
||||
<string name="common_transactions">Transactions</string>
|
||||
<string name="common_transfer">Transfer</string>
|
||||
<string name="common_understand">I understand</string>
|
||||
<string name="common_unlock_needed">Unlock needed</string>
|
||||
<string name="common_unreachable">Unreachable</string>
|
||||
<string name="common_yes">Yes</string>
|
||||
<string name="contract_address_copied_message">Contract address copied!</string>
|
||||
|
|
@ -129,7 +132,10 @@
|
|||
<string name="custom_token_creation_error_required_field">Required field</string>
|
||||
<string name="custom_token_creation_error_wrong_decimals">Decimal number must be a valid integer, no higher than %d</string>
|
||||
<string name="custom_token_custom_derivation">Custom derivation</string>
|
||||
<string name="custom_token_custom_derivation_placeholder">E. g. m/00\'/0000\'/0\'/0/0</string>
|
||||
<string name="custom_token_custom_derivation_title">Enter custom derivation</string>
|
||||
<string name="custom_token_decimals_input_title">Decimals</string>
|
||||
<string name="custom_token_derivation_path">Derivation Path</string>
|
||||
<string name="custom_token_derivation_path_default">Default</string>
|
||||
<string name="custom_token_derivation_path_input_title">BIP44 coin type</string>
|
||||
<string name="custom_token_invalid_derivation_path">The derivation path you\'ve entered is not valid</string>
|
||||
|
|
@ -137,8 +143,11 @@
|
|||
<string name="custom_token_name_input_title">Name</string>
|
||||
<string name="custom_token_network_input_not_selected">Not selected</string>
|
||||
<string name="custom_token_network_input_title">Network</string>
|
||||
<string name="custom_token_network_selector_title">Token network</string>
|
||||
<string name="custom_token_subtitle">You can manually add a token that is not natively supported by Tangem</string>
|
||||
<string name="custom_token_token_symbol_input_placeholder">E.g. USDC</string>
|
||||
<string name="custom_token_token_symbol_input_title">Token symbol</string>
|
||||
<string name="custom_token_token_symbol_input_title">Symbol</string>
|
||||
<string name="custom_token_token_symbol_input_title_old">Token symbol</string>
|
||||
<string name="custom_token_validation_error_already_added">This token/network has already been added to your list</string>
|
||||
<string name="custom_token_validation_error_not_found">Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing.</string>
|
||||
<string name="details_chat">Chat</string>
|
||||
|
|
@ -149,11 +158,13 @@
|
|||
<string name="details_manage_security_passcode">Passcode</string>
|
||||
<string name="details_manage_security_passcode_description">Before executing any command entailing a change of the card state, you will have to enter the passcode.</string>
|
||||
<string name="details_referral_title">Referral program</string>
|
||||
<string name="details_row_description_flip_to_hide">Flip your device screen down to quickly hide and show balances</string>
|
||||
<string name="details_row_privacy_policy">Privacy policy</string>
|
||||
<string name="details_row_subtitle_signed_hashes_format">%s hashes</string>
|
||||
<string name="details_row_title_cid">Card ID</string>
|
||||
<string name="details_row_title_create_backup">Link More Cards</string>
|
||||
<string name="details_row_title_currency">App Currency</string>
|
||||
<string name="details_row_title_flip_to_hide">Flip-to-Hide Balances</string>
|
||||
<string name="details_row_title_issuer">Issuer</string>
|
||||
<string name="details_row_title_send_feedback">Send Feedback</string>
|
||||
<string name="details_row_title_signed_hashes">Signed</string>
|
||||
|
|
@ -174,7 +185,7 @@
|
|||
<string name="feedback_subject_support">Feedback</string>
|
||||
<string name="feedback_subject_support_tangem">Tangem feedback</string>
|
||||
<string name="feedback_subject_tx_failed">Can\'t send a transaction</string>
|
||||
<string name="home_button_order">Order</string>
|
||||
<string name="home_button_order">Order card</string>
|
||||
<string name="home_button_scan">Scan card</string>
|
||||
<string name="initial_message_change_access_code_body">To change the access code tap the card as shown above and do not remove until the end of the operation</string>
|
||||
<string name="initial_message_change_passcode_body">To change the passcode tap the card as shown above and do not remove until the end of the operation</string>
|
||||
|
|
@ -186,26 +197,41 @@
|
|||
<string name="initial_message_tap_header">Tap the card</string>
|
||||
<string name="internal_error_wallet_manager_not_found">Internal error: wallet manager not found</string>
|
||||
<string name="key_invalidated_warning_description">You have updated biometrics, scan your card to enter</string>
|
||||
<string name="main_empty_tokens_list_message">To begin tracking your crypto assets and transactions, add tokens.</string>
|
||||
<string name="main_empty_tokens_list_message">To begin tracking your crypto assets and transactions, add tokens</string>
|
||||
<string name="main_get_bonus_subtitle">You have completed all of the lessons, and are now eligible to receive your 1INCH tokens</string>
|
||||
<plurals name="main_learn_subtitle">
|
||||
<item quantity="one">Complete three lessons and receive %d 1INCH token to your wallet</item>
|
||||
<item quantity="other">Complete three lessons and receive %d 1INCH tokens to your wallet</item>
|
||||
</plurals>
|
||||
<string name="main_manage_tokens">Manage tokens</string>
|
||||
<string name="main_no_backup_warning_subtitle">To protect your assets, we advise you to carry out this procedure</string>
|
||||
<string name="main_no_backup_warning_title">Your wallet has not been backed up</string>
|
||||
<string name="main_page_balance">Total balance</string>
|
||||
<string name="main_processing_full_amount">The amount does not include some of your funds</string>
|
||||
<string name="main_promotion_credited">1INCH tokens will be credited to your %s wallet address within 48 hours</string>
|
||||
<string name="main_scan_card_warning_view_subtitle">To access all the networks you need to scan the card</string>
|
||||
<string name="main_scan_card_warning_view_title">Scan your card</string>
|
||||
<string name="main_tokens">Tokens</string>
|
||||
<plurals name="main_warning_missing_derivation_description">
|
||||
<item quantity="one">You need to generate address for %d new network using your card</item>
|
||||
<item quantity="other">You need to generate addresses for %d new networks using your card</item>
|
||||
<string name="manage_tokens_add">Add</string>
|
||||
<string name="manage_tokens_edit">Edit</string>
|
||||
<string name="manage_tokens_network_selector_native_subtitle">Blockchain the cryptocurrency was initially created</string>
|
||||
<string name="manage_tokens_network_selector_native_title">Native network</string>
|
||||
<string name="manage_tokens_network_selector_non_native_info">Using non-native networks for tokens enables cross-blockchain interoperability, allowing assets to be utilized in diverse decentralized applications and smart contracts across platforms. However, this often involves a custodian or smart contract to hold the original asset securely, introducing centralization and counterparty risk.</string>
|
||||
<string name="manage_tokens_network_selector_non_native_subtitle">Not original or primary blockchain the token is hosted</string>
|
||||
<string name="manage_tokens_network_selector_non_native_title">Non-native networks</string>
|
||||
<string name="manage_tokens_network_selector_other_subtitle">Blockchain the cryptocurrency was initially created</string>
|
||||
<string name="manage_tokens_network_selector_other_title">Networks</string>
|
||||
<string name="manage_tokens_network_selector_title">Choose networks</string>
|
||||
<string name="manage_tokens_network_selector_wallet">Wallet</string>
|
||||
<string name="manage_tokens_nothing_found">Couldn’t find this token, you can add it manually</string>
|
||||
<string name="manage_tokens_number_of_wallets">%d of %#@total_wallets@</string>
|
||||
<plurals name="manage_tokens_number_of_walletstotal_wallets">
|
||||
<item quantity="one">%d wallet</item>
|
||||
<item quantity="other">%d wallets</item>
|
||||
</plurals>
|
||||
<string name="main_warning_missing_derivation_title">Some addresses are missing</string>
|
||||
<string name="manage_tokens_search_placeholder">e.g. BTC I trust, hodl I must</string>
|
||||
<string name="manage_tokens_title">Coin market cap</string>
|
||||
<string name="manage_tokens_unavailable_description">The selected token is currently unavailable for actions within the crypto wallet. But worry not, you can express your interest by upvoting it.</string>
|
||||
<string name="manage_tokens_unavailable_vote">Upvote</string>
|
||||
<string name="manage_tokens_wallet_selector_title">Choose wallet</string>
|
||||
<string name="onboarding_access_code_feature_1_description">You have to set up a single access code to protect all your wallets</string>
|
||||
<string name="onboarding_access_code_feature_1_title">Protect</string>
|
||||
<string name="onboarding_access_code_feature_2_description">You can set up an individual access code on each card later</string>
|
||||
|
|
@ -233,7 +259,7 @@
|
|||
<string name="onboarding_button_skip_backup">Skip for later</string>
|
||||
<string name="onboarding_button_what_does_it_mean">How does it work?</string>
|
||||
<string name="onboarding_create_wallet_body">Let\'s generate all the keys on your card and create a secure wallet</string>
|
||||
<string name="onboarding_create_wallet_button_create_wallet">Create a wallet</string>
|
||||
<string name="onboarding_create_wallet_button_create_wallet">Create wallet</string>
|
||||
<string name="onboarding_create_wallet_header">Create a wallet</string>
|
||||
<string name="onboarding_create_wallet_options_button_options">Other options</string>
|
||||
<string name="onboarding_create_wallet_options_message">Your keys will be securely generated inside the card. There is no seed phrase, which means nobody can export or steal it.</string>
|
||||
|
|
@ -334,7 +360,8 @@
|
|||
</plurals>
|
||||
<string name="registration_task_alert_message">Please hold the card until the operation complete</string>
|
||||
<string name="reset_card_to_factory_button_title">Reset the Card</string>
|
||||
<string name="reset_card_to_factory_warning_message">I understand that after performing this action, I will no longer have access to the current wallet</string>
|
||||
<string name="reset_card_to_factory_condition_1">I understand that after performing this action, I will no longer have access to the current wallet</string>
|
||||
<string name="reset_card_to_factory_condition_2">I realize that I can\'t use this card to recover my access code on the other cards of the current wallet</string>
|
||||
<string name="reset_card_with_backup_to_factory_message">Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet.</string>
|
||||
<string name="russian_bank_card_warning_subtitle">Do you have a bank card from another country and a residence permit or registration outside the Russian Federation?</string>
|
||||
|
|
@ -379,10 +406,9 @@
|
|||
<string name="shop_other_payment_methods">Other payment methods</string>
|
||||
<string name="shop_pre_order_now">Pre-order now</string>
|
||||
<string name="shop_total">Total</string>
|
||||
<string name="solana_rent_warning">Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free.</string>
|
||||
<string name="story_awe_description">Store your crypto assets secure while keeping private keys contained in your card</string>
|
||||
<string name="story_awe_title">Revolutionary Hardware Wallet</string>
|
||||
<string name="story_backup_description">Up to **3 physical cards** to one wallet</string>
|
||||
<string name="story_backup_description">Up to 3 physical cards to one wallet</string>
|
||||
<string name="story_backup_description_1">Up to</string>
|
||||
<string name="story_backup_description_2_bold">3 physical cards</string>
|
||||
<string name="story_backup_description_3">to one wallet</string>
|
||||
|
|
@ -393,16 +419,9 @@
|
|||
<string name="story_finish_title">The Wallet for Everyone</string>
|
||||
<string name="story_learn_description">Take three lessons, get a discount on your Tangem Wallet, and receive 1INCH tokens to your wallet</string>
|
||||
<string name="story_learn_learn">Learn</string>
|
||||
<string name="story_meet_borrow">Borrow</string>
|
||||
<string name="story_meet_buy">Buy</string>
|
||||
<string name="story_meet_exchange">Exchange</string>
|
||||
<string name="story_meet_lend">Lend</string>
|
||||
<string name="story_meet_pay">Pay</string>
|
||||
<string name="story_meet_send">Send</string>
|
||||
<string name="story_meet_store">Store</string>
|
||||
<string name="story_meet_title">Meet\nTangem</string>
|
||||
<string name="story_meet_title">Meet Tangem</string>
|
||||
<string name="story_web3_description">Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services</string>
|
||||
<string name="story_web3_title">DeFi Compatible</string>
|
||||
<string name="story_web3_title">Web 3.0 Compatible</string>
|
||||
<string name="swapping_approve_information_text">Approvals are considered an industry standard across all decentralized exchanges and protect your wallet from being accessed by a smart contract without your permission. By design, smart contracts can\'t access your tokens unless you approve access from your end. By \"unlocking\" your tokens, you are give permission to the 1inch smart contract to spend your assets. The miners of the network are compensated with a gas fee (paid by you) to record this action on the blockchain. Once permission has been granted you will be able to swap your token.</string>
|
||||
<string name="swapping_approve_information_title">Approve</string>
|
||||
<string name="swapping_error_wrapper">Error: %s</string>
|
||||
|
|
@ -441,8 +460,6 @@
|
|||
<string name="token_details_hide_alert_message">You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.</string>
|
||||
<string name="token_details_hide_alert_title">Hide %s</string>
|
||||
<string name="token_details_hide_token">Hide token</string>
|
||||
<string name="token_details_send_blocked_fee_format">%1$s is a token in the %2$s network. To make a %3$s transaction you need to deposit some %4$s (%5$s) to cover the network fee.</string>
|
||||
<string name="token_details_send_blocked_tx_format">Please wait for %s transaction to complete to be able to send funds</string>
|
||||
<string name="token_details_token_type_subtitle">%1$s token in %%image%% %2$s network</string>
|
||||
<string name="token_details_unable_hide_alert_message">The %1$s token is the main currency on the %2$s network and cannot be hidden as long as you have other tokens on this network in the list.</string>
|
||||
<string name="token_details_unable_hide_alert_title">Unable to hide %s</string>
|
||||
|
|
@ -467,7 +484,6 @@
|
|||
<string name="twins_recreate_warning">This action is irreversible. You will not have access to the old wallet.</string>
|
||||
<string name="twins_scan_twin_with_number">Tap the twin card with number %s and do not remove until the end of the operation</string>
|
||||
<string name="unlock_wallet_description_full">Use %s or scan a card to have an access to your wallet</string>
|
||||
<string name="unlock_wallet_description_short">Use %s or scan a card</string>
|
||||
<string name="user_wallet_list_add_button">Add new wallet</string>
|
||||
<string name="user_wallet_list_delete_prompt">Are you sure you want to delete this wallet?</string>
|
||||
<string name="user_wallet_list_editing_count">%d selected</string>
|
||||
|
|
@ -478,9 +494,9 @@
|
|||
<string name="user_wallet_list_rename_popup_title">Rename Wallet</string>
|
||||
<string name="user_wallet_list_single_header">Single-currency</string>
|
||||
<string name="user_wallet_list_title">My Wallets</string>
|
||||
<string name="user_wallet_list_unlock_all">Unlock all with %s</string>
|
||||
<string name="user_wallet_list_unlock_all">Unlock all</string>
|
||||
<string name="user_wallet_list_unlock_all_with">Unlock all with %s</string>
|
||||
<string name="wallet_address_button_explore">Transaction history</string>
|
||||
<string name="wallet_balance_blockchain_unreachable">Network is unreachable</string>
|
||||
<string name="wallet_balance_blockchain_unreachable_try_later">Blockchain is unreachable. Try later</string>
|
||||
<string name="wallet_balance_loading">Balance is loading…</string>
|
||||
<string name="wallet_balance_missing_derivation">Scan the card</string>
|
||||
|
|
@ -511,6 +527,7 @@
|
|||
<string name="wallet_connect_network_not_found_format">%s network not found. Please, add it first and try again.</string>
|
||||
<string name="wallet_connect_no_sessions_message">No opened WalletConnect sessions</string>
|
||||
<string name="wallet_connect_no_sessions_title">Ooops. No Sessions.</string>
|
||||
<string name="wallet_connect_pairing_error">Failed to pairing WalletConnect session: %1$s</string>
|
||||
<string name="wallet_connect_paste_from_clipboard">Paste from clipboard</string>
|
||||
<string name="wallet_connect_personal_sign_message">Message for %1$s:\n%2$s</string>
|
||||
<string name="wallet_connect_request_session_start">Request to start a session for\n%1$s\n\nNETWORK: %2$s\n\nURL: %3$s</string>
|
||||
|
|
@ -541,19 +558,48 @@
|
|||
<string name="wallet_pending_tx_sending">Sending</string>
|
||||
<string name="wallet_pending_tx_sending_address_format">to %s</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="warning_button_can_be_better">Can be better</string>
|
||||
<string name="warning_access_denied_message">Use %s or scan a card to unlock access to your wallet</string>
|
||||
<string name="warning_button_could_be_better">Could be better</string>
|
||||
<string name="warning_button_learn_more">Learn more</string>
|
||||
<string name="warning_button_like_it">Like it</string>
|
||||
<string name="warning_button_ok">Ok, Got it!</string>
|
||||
<string name="warning_button_really_cool">Really cool!</string>
|
||||
<string name="warning_existential_deposit_message">%1$s network has a concept of Existential Deposit. If your account drops below %2$s it will be deactivated and any remaining funds will be destroyed.</string>
|
||||
<string name="warning_demo_mode_message">You are currently in the Demo mode</string>
|
||||
<string name="warning_demo_mode_title">Demo mode active</string>
|
||||
<string name="warning_developer_card_message">The card you scanned is a developer card. Do not use it to create your wallet.</string>
|
||||
<string name="warning_developer_card_title">Not for users!</string>
|
||||
<string name="warning_existential_deposit_message">%1$s network requires an Existential Deposit. If your account drops below %2$s, it will be deactivated, and any remaining funds will be destroyed.</string>
|
||||
<string name="warning_existential_deposit_title">Network requires Existential Deposit</string>
|
||||
<string name="warning_failed_to_verify_card_message">This card might be a production sample or counterfeit</string>
|
||||
<string name="warning_failed_to_verify_card_title">Authenticity check failed</string>
|
||||
<string name="warning_important_security_info">Important security information %s</string>
|
||||
<string name="warning_low_signatures_format">There are only %s signatures available on this card. You must withdraw all of your funds.</string>
|
||||
<string name="warning_rate_app_message">How do you like Tangem?</string>
|
||||
<string name="warning_rate_app_title">One question</string>
|
||||
<string name="warning_signed_tx_previously">This card has signed transactions in the past</string>
|
||||
<string name="warning_testnet_card_message">This is a Testnet card. Don\'t accept it as a payment. This card must only be used for testing and development purposes.</string>
|
||||
<string name="warning_low_signatures_message">Only %s signatures are left on this card. You must withdraw all of your funds.</string>
|
||||
<string name="warning_low_signatures_title">Low signature count</string>
|
||||
<string name="warning_manage_tokens_legacy_derivation_message">Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds.</string>
|
||||
<plurals name="warning_missing_derivation_message">
|
||||
<item quantity="one">Use your card to generate an address for %d new network</item>
|
||||
<item quantity="other">Use your card to generate an addresses for %d new networks</item>
|
||||
</plurals>
|
||||
<string name="warning_missing_derivation_title">Some addresses are missing</string>
|
||||
<string name="warning_network_unreachable_message">The network is currently unreachable. Please try again later.</string>
|
||||
<string name="warning_network_unreachable_title">Network is unreachable</string>
|
||||
<string name="warning_no_account_title">Top up your wallet</string>
|
||||
<string name="warning_no_backup_message">Your wallet hasn\'t been backed up. Carry out this procedure to protect your assets now.</string>
|
||||
<string name="warning_no_backup_title">Missing backup</string>
|
||||
<string name="warning_number_of_signed_hashes_incorrect_message">This card has been previously used for transactions. If received from an untrusted source, consider withdrawing all funds. If it\'s your card, no action is required.</string>
|
||||
<string name="warning_number_of_signed_hashes_incorrect_title">Card has already signed transactions</string>
|
||||
<string name="warning_old_device_old_card_title">Device incompatibility detected</string>
|
||||
<string name="warning_rate_app_message">Your review keeps us motivated to make Tangem Wallet even better</string>
|
||||
<string name="warning_rate_app_title">Enjoying Tangem?</string>
|
||||
<string name="warning_rent_fee_title">Network rent fee required</string>
|
||||
<string name="warning_send_blocked_funds_for_fee_message">%1$s is a token in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee.</string>
|
||||
<string name="warning_send_blocked_funds_for_fee_title">Insufficient %1$s to cover network fee</string>
|
||||
<string name="warning_send_blocked_pending_transactions_message">Sending funds will be available once the %s transaction is complete</string>
|
||||
<string name="warning_send_blocked_pending_transactions_title">Transaction pending</string>
|
||||
<string name="warning_solana_rent_fee_message">Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free.</string>
|
||||
<string name="warning_some_networks_unreachable_message">Some networks currently are unreachable. Please try again later.</string>
|
||||
<string name="warning_some_networks_unreachable_title">Some networks are unreachable</string>
|
||||
<string name="warning_testnet_card_message">This is a Testnet card. It cannot process transactions and should only be used for testing and development purposes.</string>
|
||||
<string name="warning_testnet_card_title">For testing purposes only</string>
|
||||
<string name="welcome_interrupted_backup_alert_discard">Discard</string>
|
||||
<string name="welcome_interrupted_backup_alert_message">You have an interrupted backup. Do you want to resume?</string>
|
||||
<string name="welcome_interrupted_backup_alert_resume">Yes, resume</string>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ plugins {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
/** Project - Common */
|
||||
implementation(projects.common)
|
||||
|
||||
/** Project - Domain */
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.appTheme.models)
|
||||
|
|
@ -15,6 +18,7 @@ dependencies {
|
|||
/** AndroidX libraries */
|
||||
implementation(deps.androidx.fragment.ktx)
|
||||
implementation(deps.androidx.paging.runtime)
|
||||
implementation(deps.androidx.palette)
|
||||
|
||||
/** Compose */
|
||||
implementation(deps.compose.constraintLayout)
|
||||
|
|
@ -29,4 +33,6 @@ dependencies {
|
|||
implementation(deps.material)
|
||||
implementation(deps.compose.shimmer)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.zxing.qrCore)
|
||||
implementation(deps.jodatime)
|
||||
}
|
||||
|
|
@ -1,44 +1,33 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material.RadioButton
|
||||
import androidx.compose.material.RadioButtonDefaults
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SelctorDialogParamsProvider.SelectorDialogParams
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Dialog button params
|
||||
*
|
||||
* @param title Button text. If not provided default values will be used
|
||||
* @param warning If true then button text will be in theme warning color
|
||||
* @param enabled If false button will be disabled
|
||||
* @param onClick Button click callback
|
||||
*/
|
||||
data class DialogButton(
|
||||
val title: String? = null,
|
||||
val warning: Boolean = false,
|
||||
val enabled: Boolean = true,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
/**
|
||||
* Additional params for dialog text field
|
||||
*/
|
||||
data class AdditionalTextInputDialogParams(
|
||||
val label: String? = null,
|
||||
val placeholder: String? = null,
|
||||
val caption: String? = null,
|
||||
val enabled: Boolean = true,
|
||||
val isError: Boolean = false,
|
||||
)
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/**
|
||||
* Simple alert dialog with a message and 'OK' button
|
||||
|
|
@ -129,6 +118,54 @@ fun TextInputDialog(
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SelectorDialog(
|
||||
selectedItemIndex: Int,
|
||||
items: ImmutableList<String>,
|
||||
confirmButton: DialogButton,
|
||||
onSelect: (index: Int) -> Unit,
|
||||
onDismissDialog: () -> Unit,
|
||||
title: String? = null,
|
||||
isDismissable: Boolean = true,
|
||||
) {
|
||||
TangemDialog(
|
||||
type = DialogType.Selector(selectedItemIndex, items, onSelect),
|
||||
confirmButton = confirmButton,
|
||||
title = title,
|
||||
onDismissDialog = onDismissDialog,
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = isDismissable,
|
||||
dismissOnClickOutside = isDismissable,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog button params
|
||||
*
|
||||
* @param title Button text. If not provided default values will be used
|
||||
* @param warning If true then button text will be in theme warning color
|
||||
* @param enabled If false button will be disabled
|
||||
* @param onClick Button click callback
|
||||
*/
|
||||
data class DialogButton(
|
||||
val title: String? = null,
|
||||
val warning: Boolean = false,
|
||||
val enabled: Boolean = true,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
/**
|
||||
* Additional params for dialog text field
|
||||
*/
|
||||
data class AdditionalTextInputDialogParams(
|
||||
val label: String? = null,
|
||||
val placeholder: String? = null,
|
||||
val caption: String? = null,
|
||||
val enabled: Boolean = true,
|
||||
val isError: Boolean = false,
|
||||
)
|
||||
|
||||
// region Defaults
|
||||
@Composable
|
||||
private fun TangemDialog(
|
||||
|
|
@ -146,15 +183,18 @@ private fun TangemDialog(
|
|||
shape = TangemTheme.shapes.roundedCornersLarge,
|
||||
color = TangemTheme.colors.background.plain,
|
||||
)
|
||||
.padding(all = TangemTheme.dimens.spacing24),
|
||||
.padding(vertical = TangemTheme.dimens.spacing24),
|
||||
) {
|
||||
if (title != null) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing24)
|
||||
.fillMaxWidth(),
|
||||
text = title,
|
||||
style = when (type) {
|
||||
is DialogType.Message -> TangemTheme.typography.h2
|
||||
is DialogType.TextInput -> TangemTheme.typography.h3
|
||||
is DialogType.Selector -> TangemTheme.typography.h2
|
||||
},
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
|
@ -162,7 +202,13 @@ private fun TangemDialog(
|
|||
}
|
||||
DialogContent(type = type)
|
||||
SpacerH24()
|
||||
DialogButtons(confirmButton = confirmButton, dismissButton = dismissButton)
|
||||
DialogButtons(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing24)
|
||||
.fillMaxWidth(),
|
||||
confirmButton = confirmButton,
|
||||
dismissButton = dismissButton,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -177,7 +223,9 @@ private fun DialogContent(type: DialogType, modifier: Modifier = Modifier) {
|
|||
when (type) {
|
||||
is DialogType.Message -> {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing24)
|
||||
.fillMaxWidth(),
|
||||
text = type.message,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
|
|
@ -185,7 +233,9 @@ private fun DialogContent(type: DialogType, modifier: Modifier = Modifier) {
|
|||
}
|
||||
is DialogType.TextInput -> {
|
||||
OutlineTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing24)
|
||||
.fillMaxWidth(),
|
||||
value = type.value,
|
||||
label = type.params.label,
|
||||
placeholder = type.params.placeholder,
|
||||
|
|
@ -197,6 +247,14 @@ private fun DialogContent(type: DialogType, modifier: Modifier = Modifier) {
|
|||
},
|
||||
)
|
||||
}
|
||||
is DialogType.Selector -> {
|
||||
SelectorDialogContent(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
selectedItemIndex = type.selectedItemIndex,
|
||||
items = type.items,
|
||||
onSelect = type.onSelect,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -204,7 +262,7 @@ private fun DialogContent(type: DialogType, modifier: Modifier = Modifier) {
|
|||
@Composable
|
||||
private fun DialogButtons(confirmButton: DialogButton, dismissButton: DialogButton?, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = TangemTheme.dimens.spacing4,
|
||||
alignment = Alignment.End,
|
||||
|
|
@ -252,14 +310,72 @@ private fun DialogButton(
|
|||
}
|
||||
}
|
||||
|
||||
private sealed interface DialogType {
|
||||
data class Message(val message: String) : DialogType
|
||||
@Composable
|
||||
private fun SelectorDialogContent(
|
||||
selectedItemIndex: Int,
|
||||
items: ImmutableList<String>,
|
||||
onSelect: (index: Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(modifier = modifier) {
|
||||
itemsIndexed(items = items) { index, itemText ->
|
||||
val onClick = remember(index) {
|
||||
{ onSelect(index) }
|
||||
}
|
||||
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = LocalIndication.current,
|
||||
onClick = onClick,
|
||||
)
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing16,
|
||||
horizontal = TangemTheme.dimens.spacing18,
|
||||
)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
RadioButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
selected = index == selectedItemIndex,
|
||||
onClick = onClick,
|
||||
colors = RadioButtonDefaults.colors(
|
||||
selectedColor = TangemTheme.colors.icon.accent,
|
||||
unselectedColor = TangemTheme.colors.icon.secondary,
|
||||
),
|
||||
interactionSource = interactionSource,
|
||||
)
|
||||
Text(
|
||||
text = itemText,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
private sealed class DialogType {
|
||||
|
||||
data class Message(val message: String) : DialogType()
|
||||
|
||||
data class TextInput(
|
||||
val value: TextFieldValue,
|
||||
val onValueChange: (TextFieldValue) -> Unit,
|
||||
val params: AdditionalTextInputDialogParams = AdditionalTextInputDialogParams(),
|
||||
) : DialogType
|
||||
) : DialogType()
|
||||
|
||||
data class Selector(
|
||||
val selectedItemIndex: Int,
|
||||
val items: ImmutableList<String>,
|
||||
val onSelect: (index: Int) -> Unit,
|
||||
) : DialogType()
|
||||
}
|
||||
// endregion Defaults
|
||||
|
||||
|
|
@ -381,4 +497,65 @@ private fun TextInputDialogPreview_Dark() {
|
|||
TextInputDialogSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun SelectorDialogPreview_Light(
|
||||
@PreviewParameter(SelctorDialogParamsProvider::class) param: SelectorDialogParams,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
SelectorDialog(
|
||||
title = param.title,
|
||||
items = param.items,
|
||||
selectedItemIndex = param.selectedItemIndex,
|
||||
confirmButton = DialogButton(title = "Cancel", onClick = {}),
|
||||
onSelect = {},
|
||||
onDismissDialog = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun SelectorDialogPreview_Dark(
|
||||
@PreviewParameter(SelctorDialogParamsProvider::class) param: SelectorDialogParams,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
SelectorDialog(
|
||||
title = param.title,
|
||||
items = param.items,
|
||||
selectedItemIndex = param.selectedItemIndex,
|
||||
confirmButton = DialogButton(title = "Cancel", onClick = {}),
|
||||
onSelect = {},
|
||||
onDismissDialog = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class SelctorDialogParamsProvider : CollectionPreviewParameterProvider<SelectorDialogParams>(
|
||||
collection = listOf(
|
||||
SelectorDialogParams(
|
||||
title = "Theme",
|
||||
selectedItemIndex = 0,
|
||||
persistentListOf("Light", "Dark", "Follow system"),
|
||||
),
|
||||
SelectorDialogParams(
|
||||
title = null,
|
||||
selectedItemIndex = 2,
|
||||
persistentListOf("Light", "Dark", "Follow system"),
|
||||
),
|
||||
SelectorDialogParams(
|
||||
title = "Count",
|
||||
selectedItemIndex = 8,
|
||||
List(size = 10) { it.toString() }.toImmutableList(),
|
||||
),
|
||||
),
|
||||
) {
|
||||
|
||||
data class SelectorDialogParams(
|
||||
val title: String?,
|
||||
val selectedItemIndex: Int,
|
||||
val items: ImmutableList<String>,
|
||||
)
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.tangem.core.ui.extensions.toQrCode
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
fun rememberQrPainters(
|
||||
content: List<String>,
|
||||
size: Dp = TangemTheme.dimens.size248,
|
||||
padding: Dp = TangemTheme.dimens.spacing0,
|
||||
): List<BitmapPainter> {
|
||||
val density = LocalDensity.current
|
||||
return remember(content) {
|
||||
content.map { code ->
|
||||
BitmapPainter(
|
||||
code.toQrCode(
|
||||
sizePx = with(density) { size.roundToPx() },
|
||||
paddingPx = with(density) { padding.roundToPx() },
|
||||
).asImageBitmap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,15 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.annotation.FloatRange
|
||||
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.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
|
|
@ -19,6 +20,8 @@ fun ResizableText(
|
|||
fontSizeRange: FontSizeRange,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = Color.Unspecified,
|
||||
overflow: TextOverflow = TextOverflow.Clip,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
) {
|
||||
val fontSizeValue = remember { mutableStateOf(fontSizeRange.max.value) }
|
||||
|
|
@ -32,12 +35,13 @@ fun ResizableText(
|
|||
}
|
||||
|
||||
Text(
|
||||
modifier = modifier.drawWithContent { if (readyToDraw.value) drawContent() },
|
||||
text = text,
|
||||
modifier = modifier.drawWithContent { if (readyToDraw.value) drawContent() },
|
||||
color = color,
|
||||
softWrap = false,
|
||||
style = style,
|
||||
fontSize = fontSizeValue.value.sp,
|
||||
overflow = overflow,
|
||||
softWrap = false,
|
||||
maxLines = maxLines,
|
||||
onTextLayout = {
|
||||
if (it.hasVisualOverflow) {
|
||||
val nextFontSizeValue = fontSizeValue.value - fontSizeRange.step.value
|
||||
|
|
@ -51,6 +55,64 @@ fun ResizableText(
|
|||
readyToDraw.value = true
|
||||
}
|
||||
},
|
||||
style = style,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A Composable function that displays text which can be resized based on its content's overflow.
|
||||
*
|
||||
* This function draws text on the screen and checks if it overflows. If the text overflows,
|
||||
* its font size is reduced recursively until it either fits the available space or reaches a
|
||||
* specified minimum font size.
|
||||
*/
|
||||
@Composable
|
||||
fun ResizableText(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = Color.Unspecified,
|
||||
textAlign: TextAlign? = null,
|
||||
overflow: TextOverflow = TextOverflow.Clip,
|
||||
softWrap: Boolean = true,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
minFontSize: TextUnit = TextUnit.Unspecified,
|
||||
@FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false)
|
||||
reduceFactor: Double = 0.9,
|
||||
) {
|
||||
var fontSize by remember { mutableStateOf(style.fontSize) }
|
||||
var readyToDraw by remember { mutableStateOf(value = false) }
|
||||
|
||||
Text(
|
||||
modifier = modifier.drawWithContent {
|
||||
if (readyToDraw) drawContent()
|
||||
},
|
||||
text = text,
|
||||
color = color,
|
||||
fontSize = fontSize,
|
||||
textAlign = textAlign,
|
||||
overflow = overflow,
|
||||
softWrap = softWrap,
|
||||
maxLines = maxLines,
|
||||
style = style,
|
||||
onTextLayout = { result ->
|
||||
fun reduceFontSize() {
|
||||
val reducedFontSize = fontSize * reduceFactor
|
||||
|
||||
if (minFontSize != TextUnit.Unspecified && reducedFontSize <= minFontSize) {
|
||||
fontSize = minFontSize
|
||||
readyToDraw = true
|
||||
} else {
|
||||
fontSize = reducedFontSize
|
||||
}
|
||||
}
|
||||
|
||||
if (result.hasVisualOverflow) {
|
||||
reduceFontSize()
|
||||
} else {
|
||||
readyToDraw = true
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,25 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.RepeatMode
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.tangem.core.ui.res.LocalIsInDarkTheme
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.valentinilk.shimmer.shimmer
|
||||
import com.valentinilk.shimmer.*
|
||||
|
||||
/**
|
||||
* Rectangle shimmer item with rounded shape from DS
|
||||
|
|
@ -18,11 +28,8 @@ import com.valentinilk.shimmer.shimmer
|
|||
fun RectangleShimmer(modifier: Modifier = Modifier, radius: Dp = TangemTheme.dimens.radius6) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.shimmer()
|
||||
.background(
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
shape = RoundedCornerShape(size = radius),
|
||||
),
|
||||
.clip(RoundedCornerShape(size = radius))
|
||||
.shimmer(TangemShimmer),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -34,27 +41,67 @@ fun RectangleShimmer(modifier: Modifier = Modifier, radius: Dp = TangemTheme.dim
|
|||
fun CircleShimmer(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.shimmer()
|
||||
.background(
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
shape = CircleShape,
|
||||
),
|
||||
.clip(CircleShape)
|
||||
.shimmer(TangemShimmer),
|
||||
)
|
||||
}
|
||||
|
||||
private val TangemShimmer: Shimmer
|
||||
@Composable
|
||||
get() = rememberShimmer(
|
||||
shimmerBounds = ShimmerBounds.View,
|
||||
theme = defaultShimmerTheme.copy(
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(
|
||||
durationMillis = 800,
|
||||
easing = LinearEasing,
|
||||
delayMillis = 800,
|
||||
),
|
||||
repeatMode = RepeatMode.Restart,
|
||||
),
|
||||
shaderColors = TangemShimmerColors,
|
||||
blendMode = BlendMode.Src,
|
||||
shaderColorStops = null,
|
||||
),
|
||||
)
|
||||
|
||||
private val TangemShimmerColors: List<Color>
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() {
|
||||
val isInDarkTheme = LocalIsInDarkTheme.current
|
||||
|
||||
return buildList {
|
||||
if (isInDarkTheme) {
|
||||
TangemColorPalette.Dark3.let(::add)
|
||||
TangemColorPalette.Dark4.let(::add)
|
||||
TangemColorPalette.Dark6.let(::add)
|
||||
TangemColorPalette.Dark4.let(::add)
|
||||
TangemColorPalette.Dark3.let(::add)
|
||||
} else {
|
||||
TangemColorPalette.Light2.let(::add)
|
||||
TangemColorPalette.Light1.let(::add)
|
||||
TangemColorPalette.White.let(::add)
|
||||
TangemColorPalette.Light1.let(::add)
|
||||
TangemColorPalette.Light2.let(::add)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region preview
|
||||
|
||||
@Composable
|
||||
private fun ShimmersPreview() {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(TangemTheme.colors.background.primary),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18),
|
||||
) {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier.size(
|
||||
width = TangemTheme.dimens.size72,
|
||||
height = TangemTheme.dimens.size12,
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(TangemTheme.dimens.size24),
|
||||
)
|
||||
CircleShimmer(modifier = Modifier.size(size = TangemTheme.dimens.size42))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,29 +9,11 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.interaction.InteractionSource
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextFieldColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
|
|
@ -43,7 +25,6 @@ import androidx.compose.ui.unit.Dp
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
|
||||
/**
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=213%3A218&t=TmfD6UBHPg9uYfev-4)
|
||||
|
|
@ -121,7 +102,7 @@ private fun TangemTextField(
|
|||
if (!label.isNullOrEmpty()) {
|
||||
Text(
|
||||
text = label,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = colors.labelColor(
|
||||
enabled = enabled,
|
||||
error = isError,
|
||||
|
|
@ -179,7 +160,7 @@ private fun TangemTextField(
|
|||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
text = caption,
|
||||
style = TangemTypography.body1,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = colors.captionColor(enabled = enabled, isError = isError).value,
|
||||
)
|
||||
}
|
||||
|
|
@ -210,8 +191,8 @@ object TangemTextFieldsDefault {
|
|||
cursorColor = TangemTheme.colors.icon.primary1,
|
||||
errorCursorColor = TangemTheme.colors.icon.warning,
|
||||
focusedIndicatorColor = TangemTheme.colors.icon.primary1,
|
||||
unfocusedIndicatorColor = TangemTheme.colors.stroke.primary,
|
||||
disabledIndicatorColor = TangemTheme.colors.stroke.primary,
|
||||
unfocusedIndicatorColor = TangemTheme.colors.stroke.secondary,
|
||||
disabledIndicatorColor = TangemTheme.colors.stroke.secondary,
|
||||
errorIndicatorColor = TangemTheme.colors.icon.warning,
|
||||
leadingIconColor = TangemTheme.colors.icon.informative,
|
||||
disabledLeadingIconColor = Color.Transparent,
|
||||
|
|
|
|||
|
|
@ -4,26 +4,11 @@ import androidx.compose.animation.Crossfade
|
|||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
|
|
@ -64,7 +49,7 @@ fun ExpandableSearchView(
|
|||
onBackClick: () -> Unit,
|
||||
onSearchChange: (String) -> Unit,
|
||||
onSearchDisplayClose: () -> Unit,
|
||||
onFocusChange: (Boolean) -> Unit,
|
||||
onFocusChange: (Boolean) -> Unit = {},
|
||||
title: String? = null,
|
||||
placeholderSearchText: String = "",
|
||||
expandedInitially: Boolean = false,
|
||||
|
|
@ -169,7 +154,7 @@ private fun SubtitleView(subtitle: String, icon: Painter?) {
|
|||
text = subtitle,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.core.ui.components.bottomsheets
|
||||
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Tangem bottom sheet with custom draggable header and config
|
||||
*
|
||||
* @param config data model containing logic and ui models
|
||||
* @param content custom bottom sheet content
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TangemBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
content: @Composable ColumnScope.(TangemBottomSheetConfigContent) -> Unit,
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = config.onDismissRequest,
|
||||
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
shape = TangemTheme.shapes.bottomSheetLarge,
|
||||
dragHandle = { TangemBottomSheetDraggableHeader() },
|
||||
) {
|
||||
content(config.content)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.core.ui.components.bottomsheets
|
||||
|
||||
/**
|
||||
* Tangem bottom sheet config
|
||||
*
|
||||
* @property isShow flag that determine if bottom sheet is shown
|
||||
* @property onDismissRequest lambda be invoked when bottom sheet is dismissed
|
||||
* @property content content config
|
||||
*/
|
||||
data class TangemBottomSheetConfig(
|
||||
val isShow: Boolean,
|
||||
val onDismissRequest: () -> Unit,
|
||||
val content: TangemBottomSheetConfigContent,
|
||||
)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.core.ui.components.bottomsheets
|
||||
|
||||
/**
|
||||
* General interface for bottom sheet config model
|
||||
*/
|
||||
interface TangemBottomSheetConfigContent
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.core.ui.components.bottomsheets
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
fun TangemBottomSheetDraggableHeader() {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.height(TangemTheme.dimens.size20),
|
||||
color = TangemTheme.colors.background.primary,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(vertical = TangemTheme.dimens.spacing8)
|
||||
.size(
|
||||
width = TangemTheme.dimens.size32,
|
||||
height = TangemTheme.dimens.size4,
|
||||
)
|
||||
.background(
|
||||
color = TangemTheme.colors.icon.inactive,
|
||||
shape = TangemTheme.shapes.roundedCornersSmall,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.core.ui.components.bottomsheets.chooseaddress
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SimpleSettingsRow
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
fun ChooseAddressBottomSheet(config: TangemBottomSheetConfig) {
|
||||
if (config.content is ChooseAddressBottomSheetConfig && config.isShow) {
|
||||
TangemBottomSheet(config) { content ->
|
||||
ChooseAddressBottomSheetContent(
|
||||
content = content as ChooseAddressBottomSheetConfig,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChooseAddressBottomSheetContent(content: ChooseAddressBottomSheetConfig) {
|
||||
Column(
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
content.addressModels.forEach { addressModel ->
|
||||
SimpleSettingsRow(
|
||||
title = addressModel.type.name,
|
||||
icon = R.drawable.ic_arrow_top_right_24,
|
||||
onItemsClick = { content.onClick(addressModel) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.core.ui.components.bottomsheets.chooseaddress
|
||||
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel
|
||||
|
||||
class ChooseAddressBottomSheetConfig(
|
||||
val addressModels: List<AddressModel>,
|
||||
val onClick: (AddressModel) -> Unit,
|
||||
) : TangemBottomSheetConfigContent
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.core.ui.components.bottomsheets.tokenreceive
|
||||
|
||||
data class AddressModel(
|
||||
val value: String,
|
||||
val type: Type = Type.Default,
|
||||
) {
|
||||
enum class Type {
|
||||
Legacy, Default
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
package com.tangem.core.ui.components.bottomsheets.tokenreceive
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.MiddleEllipsisText
|
||||
import com.tangem.core.ui.components.SecondaryButtonIconStart
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.rememberQrPainters
|
||||
import com.tangem.core.ui.extensions.shareText
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
fun TokenReceiveBottomSheet(config: TangemBottomSheetConfig) {
|
||||
if (config.content is TokenReceiveBottomSheetConfig && config.isShow) {
|
||||
TangemBottomSheet(config) { content ->
|
||||
TokenReceiveBottomSheetContent(
|
||||
content = content as TokenReceiveBottomSheetConfig,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenReceiveBottomSheetContent(content: TokenReceiveBottomSheetConfig) {
|
||||
var selectedAddress by remember { mutableStateOf(content.addresses.first()) }
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing24,
|
||||
top = TangemTheme.dimens.spacing24,
|
||||
end = TangemTheme.dimens.spacing24,
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing24),
|
||||
) {
|
||||
QrCodeContent(
|
||||
content = content,
|
||||
onAddressChange = { selectedAddress = it },
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.receive_bottom_sheet_warning_message_full, content.name),
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
val context = LocalContext.current
|
||||
SecondaryButtonIconStart(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(id = R.string.common_copy),
|
||||
iconResId = R.drawable.ic_copy_24,
|
||||
onClick = {
|
||||
content.onCopyClick.invoke()
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
clipboardManager.setText(AnnotatedString(selectedAddress.value))
|
||||
Toast.makeText(context, R.string.wallet_notification_address_copied, Toast.LENGTH_SHORT).show()
|
||||
},
|
||||
)
|
||||
SecondaryButtonIconStart(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(id = R.string.common_share),
|
||||
iconResId = R.drawable.ic_share_24,
|
||||
onClick = {
|
||||
content.onShareClick.invoke()
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
context.shareText(selectedAddress.value)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun QrCodeContent(content: TokenReceiveBottomSheetConfig, onAddressChange: (AddressModel) -> Unit) {
|
||||
val qrCodes = rememberQrPainters(content.addresses.map(AddressModel::value))
|
||||
val pagerState = rememberPagerState(
|
||||
initialPage = 0,
|
||||
initialPageOffsetFraction = 0f,
|
||||
) {
|
||||
content.addresses.count()
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = pagerState.currentPage) {
|
||||
onAddressChange.invoke(content.addresses[pagerState.currentPage])
|
||||
}
|
||||
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
) { currentPage ->
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.receive_bottom_sheet_warning_message,
|
||||
getName(content = content, index = pagerState.currentPage),
|
||||
content.symbol,
|
||||
content.network,
|
||||
),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTheme.typography.h3,
|
||||
)
|
||||
Image(
|
||||
painter = qrCodes[currentPage],
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size248),
|
||||
)
|
||||
MiddleEllipsisText(
|
||||
text = content.addresses[currentPage].value,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (pagerState.pageCount > 1) {
|
||||
val indicatorState = rememberLazyListState()
|
||||
val selectedColor = TangemTheme.colors.icon.primary1
|
||||
val unselectedColor = TangemTheme.colors.icon.informative
|
||||
LazyRow(
|
||||
modifier = Modifier
|
||||
.height(TangemTheme.dimens.size20),
|
||||
state = indicatorState,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
repeat(pagerState.pageCount) { iteration ->
|
||||
item(key = iteration) {
|
||||
val color = if (pagerState.currentPage == iteration) {
|
||||
selectedColor
|
||||
} else {
|
||||
unselectedColor
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing4,
|
||||
top = TangemTheme.dimens.spacing6,
|
||||
end = TangemTheme.dimens.spacing4,
|
||||
bottom = TangemTheme.dimens.spacing6,
|
||||
)
|
||||
.background(color, CircleShape)
|
||||
.size(TangemTheme.dimens.size7),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getName(content: TokenReceiveBottomSheetConfig, index: Int): String {
|
||||
return if (content.addresses.size < 2) {
|
||||
content.name
|
||||
} else {
|
||||
"${
|
||||
stringResource(
|
||||
id = when (content.addresses[index].type) {
|
||||
AddressModel.Type.Default -> R.string.address_type_default
|
||||
AddressModel.Type.Legacy -> R.string.address_type_legacy
|
||||
},
|
||||
)
|
||||
} ${content.name}"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.core.ui.components.bottomsheets.tokenreceive
|
||||
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
|
||||
class TokenReceiveBottomSheetConfig(
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val network: String,
|
||||
val addresses: List<AddressModel>,
|
||||
val onCopyClick: () -> Unit,
|
||||
val onShareClick: () -> Unit,
|
||||
) : TangemBottomSheetConfigContent
|
||||
|
|
@ -31,9 +31,9 @@ fun HorizontalActionChips(
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
// do not use key cause when change items order, list is scrolled
|
||||
items(
|
||||
items = buttons,
|
||||
key = { config -> config.text.hashCode() },
|
||||
itemContent = { ActionButton(config = it) },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerW8
|
||||
import com.tangem.core.ui.components.buttons.common.*
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -99,7 +98,7 @@ private fun Button(
|
|||
val iconTint by animateColorAsState(
|
||||
targetValue = when {
|
||||
!config.enabled -> TangemTheme.colors.icon.informative
|
||||
config.dimContent -> TangemTheme.colors.icon.secondary
|
||||
config.dimContent -> TangemTheme.colors.icon.informative
|
||||
else -> TangemTheme.colors.icon.primary1
|
||||
},
|
||||
label = "Update tint color",
|
||||
|
|
@ -117,7 +116,7 @@ private fun Button(
|
|||
val textColor by animateColorAsState(
|
||||
targetValue = when {
|
||||
!config.enabled -> TangemTheme.colors.text.disabled
|
||||
config.dimContent -> TangemTheme.colors.text.secondary
|
||||
config.dimContent -> TangemTheme.colors.text.tertiary
|
||||
else -> TangemTheme.colors.text.primary1
|
||||
},
|
||||
label = "Update text color",
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
package com.tangem.core.ui.components.buttons.common
|
||||
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.requiredSizeIn
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SpacerW
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.core.ui.components.ResizableText
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -40,31 +39,35 @@ fun TangemButton(
|
|||
colors = colors,
|
||||
contentPadding = size.toContentPadding(icon = icon),
|
||||
) {
|
||||
val maxContentSize = getMaxButtonContentSize(buttonTextStyle = textStyle)
|
||||
|
||||
ButtonContentContainer(
|
||||
buttonIcon = icon,
|
||||
iconPadding = size.toIconPadding(),
|
||||
showProgress = showProgress,
|
||||
progressIndicator = {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.buttonContentSize(textStyle),
|
||||
modifier = Modifier.buttonContentSize(maxContentSize),
|
||||
color = colors.contentColor(enabled = enabled).value,
|
||||
strokeWidth = TangemTheme.dimens.size4,
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
ResizableText(
|
||||
modifier = Modifier
|
||||
.weight(1f, fill = false)
|
||||
.heightIn(MinButtonContentSize, maxContentSize),
|
||||
text = text,
|
||||
style = textStyle,
|
||||
color = colors.contentColor(enabled = enabled).value,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
minFontSize = 12.sp,
|
||||
)
|
||||
},
|
||||
icon = { iconResId ->
|
||||
Icon(
|
||||
modifier = Modifier.buttonContentSize(textStyle),
|
||||
modifier = Modifier.buttonContentSize(maxContentSize),
|
||||
painter = painterResource(id = iconResId),
|
||||
tint = colors.contentColor(enabled = enabled).value,
|
||||
contentDescription = null,
|
||||
|
|
@ -89,26 +92,36 @@ private inline fun RowScope.ButtonContentContainer(
|
|||
} else {
|
||||
if (buttonIcon is TangemButtonIconPosition.Start) {
|
||||
icon(buttonIcon.iconResId)
|
||||
SpacerW(width = iconPadding)
|
||||
Spacer(modifier = Modifier.requiredWidth(iconPadding))
|
||||
}
|
||||
text()
|
||||
if (buttonIcon is TangemButtonIconPosition.End) {
|
||||
SpacerW(width = iconPadding)
|
||||
Spacer(modifier = Modifier.requiredWidth(iconPadding))
|
||||
icon(buttonIcon.iconResId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Modifier.buttonContentSize(buttonTextStyle: TextStyle): Modifier = composed {
|
||||
val minContentElementSize = TangemTheme.dimens.size20
|
||||
val maxContentElementSize = remember(key1 = buttonTextStyle.lineHeight) {
|
||||
buttonTextStyle.lineHeight.value.dp + 4.dp
|
||||
private val MinButtonContentSize: Dp
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemTheme.dimens.size20
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun getMaxButtonContentSize(buttonTextStyle: TextStyle): Dp {
|
||||
val buttonLineHeight = with(LocalDensity.current) {
|
||||
buttonTextStyle.lineHeight.toDp()
|
||||
}
|
||||
|
||||
return buttonLineHeight.coerceAtLeast(MinButtonContentSize)
|
||||
}
|
||||
|
||||
private fun Modifier.buttonContentSize(maxSize: Dp): Modifier = composed {
|
||||
this.requiredSizeIn(
|
||||
minWidth = minContentElementSize,
|
||||
minHeight = minContentElementSize,
|
||||
maxWidth = maxContentElementSize,
|
||||
maxHeight = maxContentElementSize,
|
||||
minWidth = MinButtonContentSize,
|
||||
minHeight = MinButtonContentSize,
|
||||
maxWidth = maxSize,
|
||||
maxHeight = maxSize,
|
||||
)
|
||||
}
|
||||
|
|
@ -2,10 +2,10 @@ package com.tangem.core.ui.components.marketprice
|
|||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -51,7 +51,7 @@ fun MarketPriceBlock(state: MarketPriceBlockState, modifier: Modifier = Modifier
|
|||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
Title(currencyName = state.currencyName)
|
||||
Title(currencyName = state.currencySymbol)
|
||||
|
||||
Content(state = state, rootWidth = rootWidth)
|
||||
}
|
||||
|
|
@ -130,27 +130,31 @@ private fun Price(price: String, modifier: Modifier = Modifier) {
|
|||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun PriceChangeInPercent(config: PriceChangeConfig) {
|
||||
private fun PriceChangeInPercent(config: PriceChangeState.Content) {
|
||||
AnimatedContent(targetState = config.type, label = "Update price change") { type ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing4),
|
||||
) {
|
||||
Image(
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
id = when (type) {
|
||||
PriceChangeConfig.Type.UP -> R.drawable.img_arrow_up_8
|
||||
PriceChangeConfig.Type.DOWN -> R.drawable.img_arrow_down_8
|
||||
PriceChangeType.UP -> R.drawable.ic_arrow_up_8
|
||||
PriceChangeType.DOWN -> R.drawable.ic_arrow_down_8
|
||||
},
|
||||
),
|
||||
tint = when (type) {
|
||||
PriceChangeType.UP -> TangemTheme.colors.icon.accent
|
||||
PriceChangeType.DOWN -> TangemTheme.colors.icon.warning
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = config.valueInPercent,
|
||||
color = when (type) {
|
||||
PriceChangeConfig.Type.UP -> TangemTheme.colors.text.accent
|
||||
PriceChangeConfig.Type.DOWN -> TangemTheme.colors.text.warning
|
||||
PriceChangeType.UP -> TangemTheme.colors.text.accent
|
||||
PriceChangeType.DOWN -> TangemTheme.colors.text.warning
|
||||
},
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
|
|
@ -209,22 +213,22 @@ private fun Preview_MarketPriceBlock_Dark(
|
|||
private class WalletMarketPriceBlockStateProvider : CollectionPreviewParameterProvider<MarketPriceBlockState>(
|
||||
collection = listOf(
|
||||
MarketPriceBlockState.Content(
|
||||
currencyName = "BTC",
|
||||
currencySymbol = "BTC",
|
||||
price = "98900 $",
|
||||
priceChangeConfig = PriceChangeConfig(
|
||||
priceChangeConfig = PriceChangeState.Content(
|
||||
valueInPercent = "5.16%",
|
||||
type = PriceChangeConfig.Type.DOWN,
|
||||
type = PriceChangeType.DOWN,
|
||||
),
|
||||
),
|
||||
MarketPriceBlockState.Content(
|
||||
currencyName = "BTC",
|
||||
currencySymbol = "BTC",
|
||||
price = "98900 $",
|
||||
priceChangeConfig = PriceChangeConfig(
|
||||
priceChangeConfig = PriceChangeState.Content(
|
||||
valueInPercent = "10.89%",
|
||||
type = PriceChangeConfig.Type.UP,
|
||||
type = PriceChangeType.UP,
|
||||
),
|
||||
),
|
||||
MarketPriceBlockState.Loading(currencyName = "BTC"),
|
||||
MarketPriceBlockState.Error(currencyName = "BTC"),
|
||||
MarketPriceBlockState.Loading(currencySymbol = "BTC"),
|
||||
MarketPriceBlockState.Error(currencySymbol = "BTC"),
|
||||
),
|
||||
)
|
||||
|
|
@ -5,15 +5,15 @@ import androidx.compose.runtime.Immutable
|
|||
@Immutable
|
||||
sealed interface MarketPriceBlockState {
|
||||
|
||||
val currencyName: String
|
||||
val currencySymbol: String
|
||||
|
||||
data class Error(override val currencyName: String) : MarketPriceBlockState
|
||||
data class Error(override val currencySymbol: String) : MarketPriceBlockState
|
||||
|
||||
data class Loading(override val currencyName: String) : MarketPriceBlockState
|
||||
data class Loading(override val currencySymbol: String) : MarketPriceBlockState
|
||||
|
||||
data class Content(
|
||||
override val currencyName: String,
|
||||
override val currencySymbol: String,
|
||||
val price: String,
|
||||
val priceChangeConfig: PriceChangeConfig,
|
||||
val priceChangeConfig: PriceChangeState.Content,
|
||||
) : MarketPriceBlockState
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package com.tangem.core.ui.components.marketprice
|
||||
|
||||
data class PriceChangeConfig(val valueInPercent: String, val type: Type) {
|
||||
|
||||
/** Price changing type */
|
||||
enum class Type {
|
||||
UP, DOWN
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.core.ui.components.marketprice
|
||||
|
||||
sealed class PriceChangeState {
|
||||
|
||||
data class Content(val valueInPercent: String, val type: PriceChangeType) : PriceChangeState()
|
||||
|
||||
object Unknown : PriceChangeState()
|
||||
}
|
||||
|
||||
/** Price changing type */
|
||||
enum class PriceChangeType {
|
||||
UP, DOWN
|
||||
}
|
||||
|
|
@ -1,112 +1,140 @@
|
|||
package com.tangem.core.ui.components.notifications
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH2
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState as NotificationButtonsState
|
||||
|
||||
/**
|
||||
* Notification component from Design system.
|
||||
* Use this for Notification with title, subtitle, clickable or not.
|
||||
*
|
||||
* @param state component state
|
||||
* @param config component config
|
||||
* @param modifier modifier
|
||||
* @param iconTint icon tint
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=1045-807&t=6CVvYDJe0sB7wBKE-0"
|
||||
* >Figma component</a>
|
||||
*/
|
||||
@Composable
|
||||
fun Notification(state: NotificationState, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(shape = RoundedCornerShape(size = TangemTheme.dimens.radius18))
|
||||
.background(
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
shape = RoundedCornerShape(size = TangemTheme.dimens.radius18),
|
||||
)
|
||||
.clickable(
|
||||
enabled = when (state) {
|
||||
is NotificationState.Clickable -> true
|
||||
is NotificationState.Simple, is NotificationState.Closable -> false
|
||||
},
|
||||
onClick = if (state is NotificationState.Clickable) {
|
||||
state.onClick
|
||||
} else {
|
||||
{}
|
||||
},
|
||||
),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing12, vertical = TangemTheme.dimens.spacing8),
|
||||
fun Notification(config: NotificationConfig, modifier: Modifier = Modifier, iconTint: Color? = null) {
|
||||
BaseContainer(buttonsState = config.buttonsState, onClick = config.onClick, modifier = modifier) {
|
||||
Column(
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing12),
|
||||
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
NotificationIcon(
|
||||
iconResId = state.iconResId,
|
||||
iconTint = state.tint,
|
||||
MainContent(
|
||||
iconResId = config.iconResId,
|
||||
iconTint = iconTint,
|
||||
title = config.title,
|
||||
subtitle = config.subtitle,
|
||||
isClickableComponent = config.onClick != null,
|
||||
)
|
||||
|
||||
Buttons(state = config.buttonsState)
|
||||
}
|
||||
|
||||
CloseableIconButton(
|
||||
onClick = config.onCloseClick,
|
||||
modifier = Modifier.align(alignment = Alignment.TopEnd),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BaseContainer(
|
||||
buttonsState: NotificationConfig.ButtonsState?,
|
||||
onClick: (() -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
val containerColor by rememberUpdatedState(
|
||||
newValue = if (buttonsState != null || onClick != null) {
|
||||
TangemTheme.colors.background.primary
|
||||
} else {
|
||||
TangemTheme.colors.button.disabled
|
||||
},
|
||||
)
|
||||
|
||||
Surface(
|
||||
onClick = onClick ?: {},
|
||||
modifier = modifier
|
||||
.defaultMinSize(minHeight = TangemTheme.dimens.size62)
|
||||
.fillMaxWidth(),
|
||||
enabled = onClick != null,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
color = containerColor,
|
||||
) {
|
||||
Box(content = content)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainContent(
|
||||
iconResId: Int,
|
||||
iconTint: Color?,
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
isClickableComponent: Boolean,
|
||||
) {
|
||||
Row {
|
||||
Icon(
|
||||
iconResId = iconResId,
|
||||
tint = iconTint,
|
||||
modifier = Modifier.align(alignment = Alignment.CenterVertically),
|
||||
)
|
||||
|
||||
SpacerW(width = TangemTheme.dimens.spacing10)
|
||||
|
||||
TextsBlock(title = title, subtitle = subtitle)
|
||||
|
||||
if (isClickableComponent) {
|
||||
SpacerWMax()
|
||||
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_chevron_right_24),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(size = TangemTheme.dimens.size20)
|
||||
.align(alignment = Alignment.CenterStart),
|
||||
.align(alignment = Alignment.CenterVertically),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
|
||||
NotificationInfoBlock(
|
||||
title = state.title.resolveReference(),
|
||||
subtitle = state.subtitle?.resolveReference(),
|
||||
modifier = Modifier.align(alignment = Alignment.CenterStart),
|
||||
)
|
||||
|
||||
if (state is NotificationState.Closable) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(size = TangemTheme.dimens.size20)
|
||||
.align(alignment = Alignment.TopEnd),
|
||||
painter = painterResource(id = R.drawable.ic_close_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
}
|
||||
|
||||
if (state is NotificationState.Clickable) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(size = TangemTheme.dimens.size20)
|
||||
.align(alignment = Alignment.CenterEnd),
|
||||
painter = painterResource(id = R.drawable.ic_chevron_right_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NotificationIcon(@DrawableRes iconResId: Int, iconTint: Color?, modifier: Modifier = Modifier) {
|
||||
if (iconTint != null) {
|
||||
private fun Icon(@DrawableRes iconResId: Int, tint: Color?, modifier: Modifier = Modifier) {
|
||||
if (tint != null) {
|
||||
Icon(
|
||||
painter = painterResource(id = iconResId),
|
||||
contentDescription = null,
|
||||
modifier = modifier,
|
||||
tint = iconTint,
|
||||
tint = tint,
|
||||
)
|
||||
} else {
|
||||
Image(
|
||||
|
|
@ -118,20 +146,104 @@ private fun NotificationIcon(@DrawableRes iconResId: Int, iconTint: Color?, modi
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun NotificationInfoBlock(title: String, subtitle: String?, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing30)) {
|
||||
private fun TextsBlock(title: TextReference, subtitle: TextReference) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing2)) {
|
||||
Text(
|
||||
text = title,
|
||||
text = title.resolveReference(),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.body2,
|
||||
style = TangemTheme.typography.button,
|
||||
)
|
||||
|
||||
if (!subtitle.isNullOrEmpty()) {
|
||||
SpacerH2()
|
||||
Text(
|
||||
text = subtitle,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption,
|
||||
Text(
|
||||
text = subtitle.resolveReference(),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Buttons(state: NotificationButtonsState?) {
|
||||
when (state) {
|
||||
is NotificationButtonsState.SecondaryButtonConfig -> SingleSecondaryButton(config = state)
|
||||
is NotificationButtonsState.PrimaryButtonConfig -> SinglePrimaryButton(config = state)
|
||||
is NotificationButtonsState.PairButtonsConfig -> PairButtons(config = state)
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButtonConfig) {
|
||||
SecondaryButton(
|
||||
text = config.text.resolveReference(),
|
||||
onClick = config.onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonConfig) {
|
||||
if (config.iconResId != null) {
|
||||
PrimaryButtonIconEnd(
|
||||
text = config.text.resolveReference(),
|
||||
iconResId = config.iconResId,
|
||||
onClick = config.onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
} else {
|
||||
PrimaryButton(
|
||||
text = config.text.resolveReference(),
|
||||
onClick = config.onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8)) {
|
||||
SecondaryButton(
|
||||
text = config.secondaryText.resolveReference(),
|
||||
onClick = config.onSecondaryClick,
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
)
|
||||
|
||||
PrimaryButton(
|
||||
text = config.primaryText.resolveReference(),
|
||||
onClick = config.onPrimaryClick,
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Modifier) {
|
||||
AnimatedVisibility(visible = onClick != null, modifier = modifier) {
|
||||
onClick ?: return@AnimatedVisibility
|
||||
|
||||
/*
|
||||
* Implement a custom ripple because the design layout doesn't match the Material Design.
|
||||
* Material Icon has a size 24x24 and Material IconButton has a size 48x48,
|
||||
* but icon from Figma has a size 16x16.
|
||||
*/
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(size = TangemTheme.dimens.size40)
|
||||
.clip(shape = CircleShape)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = LocalIndication.current,
|
||||
role = Role.Button,
|
||||
onClick = onClick,
|
||||
),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_close_24),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(size = TangemTheme.dimens.size16)
|
||||
.align(alignment = Alignment.Center),
|
||||
tint = TangemTheme.colors.icon.inactive,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -139,72 +251,83 @@ private fun NotificationInfoBlock(title: String, subtitle: String?, modifier: Mo
|
|||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_WarningNotification_Light(
|
||||
@PreviewParameter(NotificationStateProvider::class)
|
||||
state: NotificationState,
|
||||
private fun Preview_Notification_Light(
|
||||
@PreviewParameter(NotificationConfigProvider::class)
|
||||
config: NotificationConfig,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
Notification(state)
|
||||
Notification(config)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_WarningNotification_Dark(
|
||||
@PreviewParameter(NotificationStateProvider::class)
|
||||
state: NotificationState,
|
||||
private fun Preview_Notification_Dark(
|
||||
@PreviewParameter(NotificationConfigProvider::class) config: NotificationConfig,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
Notification(state)
|
||||
Notification(config)
|
||||
}
|
||||
}
|
||||
|
||||
private class NotificationStateProvider : CollectionPreviewParameterProvider<NotificationState>(
|
||||
private class NotificationConfigProvider : CollectionPreviewParameterProvider<NotificationConfig>(
|
||||
collection = listOf(
|
||||
NotificationState.Simple(
|
||||
title = TextReference.Str(value = "Your wallet hasn’t been backed up"),
|
||||
NotificationConfig(
|
||||
title = TextReference.Str(value = "Development card"),
|
||||
subtitle = TextReference.Str(
|
||||
value = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt " +
|
||||
"ut labore et...",
|
||||
value = "The card you scanned is a development card.\nDon’t accept it as a payment.",
|
||||
),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
),
|
||||
NotificationState.Simple(
|
||||
title = TextReference.Str("Your wallet hasn’t been backed up"),
|
||||
subtitle = null,
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
tint = TangemColorPalette.Amaranth,
|
||||
),
|
||||
NotificationState.Clickable(
|
||||
title = TextReference.Str(value = "Your wallet hasn’t been backed up"),
|
||||
subtitle = TextReference.Str(
|
||||
value = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt " +
|
||||
"ut labore et...",
|
||||
),
|
||||
NotificationConfig(
|
||||
title = TextReference.Str(value = "Some networks are unreachable"),
|
||||
subtitle = TextReference.Str(value = "Check your network connection"),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
),
|
||||
NotificationConfig(
|
||||
title = TextReference.Str(value = "Used card"),
|
||||
subtitle = TextReference.Str(value = "The card signed transactions in the past"),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
onClick = {},
|
||||
),
|
||||
NotificationState.Clickable(
|
||||
NotificationConfig(
|
||||
title = TextReference.Str(value = "Your wallet hasn’t been backed up"),
|
||||
subtitle = null,
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
tint = TangemColorPalette.Amaranth,
|
||||
onClick = {},
|
||||
),
|
||||
NotificationState.Closable(
|
||||
title = TextReference.Str(value = "Your wallet hasn’t been backed up"),
|
||||
subtitle = TextReference.Str(
|
||||
value = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt " +
|
||||
"ut labore et...",
|
||||
),
|
||||
subtitle = TextReference.Str(value = "To protect your assets, we advise you to carry out this procedure"),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
onCloseClick = {},
|
||||
buttonsState = NotificationButtonsState.SecondaryButtonConfig(
|
||||
text = TextReference.Str(value = "Start backup process"),
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
NotificationState.Closable(
|
||||
title = TextReference.Str(value = "Your wallet hasn’t been backed up"),
|
||||
subtitle = null,
|
||||
NotificationConfig(
|
||||
title = TextReference.Str(value = "Some addresses are missing"),
|
||||
subtitle = TextReference.Str(value = "Generate addresses for 2 new networks using your card"),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
tint = TangemColorPalette.Amaranth,
|
||||
buttonsState = NotificationButtonsState.PrimaryButtonConfig(
|
||||
text = TextReference.Str(value = "Generate addresses"),
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
NotificationConfig(
|
||||
title = TextReference.Str(value = "Rate the app"),
|
||||
subtitle = TextReference.Str(value = "How do you like Tangem?"),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
buttonsState = NotificationButtonsState.PairButtonsConfig(
|
||||
primaryText = TextReference.Str(value = "Love it!"),
|
||||
onPrimaryClick = {},
|
||||
secondaryText = TextReference.Str(value = "Can be better"),
|
||||
onSecondaryClick = {},
|
||||
),
|
||||
),
|
||||
NotificationConfig(
|
||||
title = TextReference.Str(value = "Note top up"),
|
||||
subtitle = TextReference.Str(value = "To activate card top up it with at least 1 XLM"),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
buttonsState = NotificationButtonsState.SecondaryButtonConfig(
|
||||
text = TextReference.Str(value = "Top up card"),
|
||||
onClick = {},
|
||||
),
|
||||
onCloseClick = {},
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.core.ui.components.notifications
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
/**
|
||||
* Notification component state
|
||||
*
|
||||
* @property title title
|
||||
* @property subtitle subtitle
|
||||
* @property iconResId icon resource id
|
||||
* @property buttonsState buttons state
|
||||
* @property onClick lambda be invoked when notification is clicked
|
||||
* @property onCloseClick lambda be invoked when close button is clicked
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class NotificationConfig(
|
||||
val title: TextReference,
|
||||
val subtitle: TextReference,
|
||||
@DrawableRes val iconResId: Int,
|
||||
val buttonsState: ButtonsState? = null,
|
||||
val onClick: (() -> Unit)? = null,
|
||||
val onCloseClick: (() -> Unit)? = null,
|
||||
) {
|
||||
|
||||
sealed class ButtonsState {
|
||||
|
||||
data class PrimaryButtonConfig(
|
||||
val text: TextReference,
|
||||
@DrawableRes val iconResId: Int? = null,
|
||||
val onClick: () -> Unit,
|
||||
) : ButtonsState()
|
||||
|
||||
data class SecondaryButtonConfig(val text: TextReference, val onClick: () -> Unit) : ButtonsState()
|
||||
|
||||
data class PairButtonsConfig(
|
||||
val primaryText: TextReference,
|
||||
val onPrimaryClick: () -> Unit,
|
||||
val secondaryText: TextReference,
|
||||
val onSecondaryClick: () -> Unit,
|
||||
) : ButtonsState()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
package com.tangem.core.ui.components.notifications
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
/**
|
||||
* Notification component state
|
||||
*
|
||||
* @property title title
|
||||
* @property subtitle subtitle
|
||||
* @property iconResId icon resource id
|
||||
* @property tint icon tint
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class NotificationState(
|
||||
open val title: TextReference,
|
||||
open val subtitle: TextReference? = null,
|
||||
@DrawableRes open val iconResId: Int,
|
||||
open val tint: Color? = null,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Simple notification state. Non clickable.
|
||||
*
|
||||
* @property title title
|
||||
* @property subtitle subtitle
|
||||
* @property iconResId icon resource id
|
||||
* @property tint icon tint
|
||||
*/
|
||||
data class Simple(
|
||||
override val title: TextReference,
|
||||
override val subtitle: TextReference? = null,
|
||||
@DrawableRes override val iconResId: Int,
|
||||
override val tint: Color? = null,
|
||||
) : NotificationState(title, subtitle, iconResId, tint)
|
||||
|
||||
/**
|
||||
* Clickable notification state
|
||||
*
|
||||
* @property title title
|
||||
* @property subtitle subtitle
|
||||
* @property iconResId icon resource id
|
||||
* @property tint icon tint
|
||||
* @property onClick lambda be invoked when notification component is clicked
|
||||
*/
|
||||
data class Clickable(
|
||||
override val title: TextReference,
|
||||
override val subtitle: TextReference? = null,
|
||||
@DrawableRes override val iconResId: Int,
|
||||
override val tint: Color? = null,
|
||||
val onClick: () -> Unit,
|
||||
) : NotificationState(title, subtitle, iconResId, tint)
|
||||
|
||||
/**
|
||||
* Closable notification state
|
||||
*
|
||||
* @property title title
|
||||
* @property subtitle subtitle
|
||||
* @property iconResId icon resource id
|
||||
* @property tint icon tint
|
||||
* @property onCloseClick lambda be invoked when close button is clicked
|
||||
*/
|
||||
data class Closable(
|
||||
override val title: TextReference,
|
||||
override val subtitle: TextReference? = null,
|
||||
@DrawableRes override val iconResId: Int,
|
||||
override val tint: Color? = null,
|
||||
val onCloseClick: (() -> Unit)? = null,
|
||||
) : NotificationState(title, subtitle, iconResId, tint)
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.core.ui.components.transactions
|
|||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
|
|
@ -19,10 +20,13 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.constraintlayout.compose.Dimension
|
||||
import com.tangem.common.Strings
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.transactions.state.TransactionState
|
||||
import com.tangem.core.ui.components.transactions.state.TransactionState.Content.Direction
|
||||
import com.tangem.core.ui.components.transactions.state.TransactionState.Content.Status
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -31,8 +35,9 @@ import java.util.UUID
|
|||
/**
|
||||
* Transaction component
|
||||
*
|
||||
* @param state state
|
||||
* @param modifier modifier
|
||||
* @param state state
|
||||
* @param isBalanceHidden is balance hidden
|
||||
* @param modifier modifier
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/RU7AIgwHtGdMfy83T5UOoR/iOS?type=design&node-id=446-438&t=71jPDxMMk4e0
|
||||
* a025-4">Figma Component</a>
|
||||
|
|
@ -40,15 +45,20 @@ import java.util.UUID
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun Transaction(state: TransactionState, modifier: Modifier = Modifier) {
|
||||
fun Transaction(state: TransactionState, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.defaultMinSize(minHeight = TangemTheme.dimens.size56)
|
||||
.clickable(
|
||||
enabled = state is TransactionState.Content,
|
||||
onClick = (state as? TransactionState.Content)?.onClick ?: {},
|
||||
)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing12, vertical = TangemTheme.dimens.spacing10),
|
||||
color = TangemTheme.colors.background.primary,
|
||||
) {
|
||||
@Suppress("DestructuringDeclarationWithTooManyEntries")
|
||||
// FIXME: split this composable to small composables(loading/content/error) to properly handle onClick
|
||||
ConstraintLayout(modifier = Modifier.fillMaxWidth()) {
|
||||
val (iconItem, titleItem, subtitleItem, amountItem, timestampItem) = createRefs()
|
||||
|
||||
|
|
@ -87,6 +97,7 @@ fun Transaction(state: TransactionState, modifier: Modifier = Modifier) {
|
|||
|
||||
Amount(
|
||||
state = state,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = Modifier.constrainAs(amountItem) {
|
||||
start.linkTo(titleItem.end)
|
||||
top.linkTo(titleItem.top)
|
||||
|
|
@ -108,6 +119,7 @@ fun Transaction(state: TransactionState, modifier: Modifier = Modifier) {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
@Composable
|
||||
private fun Icon(state: TransactionState, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
|
|
@ -116,44 +128,39 @@ private fun Icon(state: TransactionState, modifier: Modifier = Modifier) {
|
|||
modifier = modifier
|
||||
.size(TangemTheme.dimens.size40)
|
||||
.background(
|
||||
color = when (state) {
|
||||
is TransactionState.ProcessedTransactionContent -> {
|
||||
TangemTheme.colors.icon.attention.copy(alpha = 0.1f)
|
||||
}
|
||||
is TransactionState.CompletedTransactionContent -> {
|
||||
TangemTheme.colors.background.secondary
|
||||
}
|
||||
color = when (state.status) {
|
||||
is Status.Unconfirmed -> TangemTheme.colors.icon.attention.copy(alpha = 0.1f)
|
||||
is Status.Confirmed -> TangemTheme.colors.background.secondary
|
||||
is Status.Failed -> TangemTheme.colors.icon.warning.copy(alpha = 0.1f)
|
||||
},
|
||||
shape = CircleShape,
|
||||
),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
id = when (state) {
|
||||
is TransactionState.Sending,
|
||||
is TransactionState.Send,
|
||||
-> R.drawable.ic_arrow_up_24
|
||||
|
||||
is TransactionState.Receiving,
|
||||
is TransactionState.Receive,
|
||||
-> R.drawable.ic_arrow_down_24
|
||||
|
||||
is TransactionState.Approving,
|
||||
is TransactionState.Approved,
|
||||
-> R.drawable.ic_doc_24
|
||||
|
||||
is TransactionState.Swapping,
|
||||
is TransactionState.Swapped,
|
||||
-> R.drawable.ic_exchange_vertical_24
|
||||
id = when (state.status) {
|
||||
is Status.Failed -> R.drawable.ic_close_24
|
||||
else -> when (state) {
|
||||
is TransactionState.Transfer -> {
|
||||
when (state.direction) {
|
||||
Direction.OUTGOING -> R.drawable.ic_arrow_up_24
|
||||
Direction.INCOMING -> R.drawable.ic_arrow_down_24
|
||||
}
|
||||
}
|
||||
is TransactionState.Approve -> R.drawable.ic_doc_24
|
||||
is TransactionState.Swap -> R.drawable.ic_exchange_vertical_24
|
||||
is TransactionState.Custom -> R.drawable.ic_exchange_vertical_24
|
||||
}
|
||||
},
|
||||
),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size20)
|
||||
.align(Alignment.Center),
|
||||
tint = when (state) {
|
||||
is TransactionState.ProcessedTransactionContent -> TangemTheme.colors.icon.attention
|
||||
is TransactionState.CompletedTransactionContent -> TangemTheme.colors.icon.informative
|
||||
tint = when (state.status) {
|
||||
Status.Confirmed -> TangemTheme.colors.icon.informative
|
||||
Status.Unconfirmed -> TangemTheme.colors.icon.attention
|
||||
Status.Failed -> TangemTheme.colors.icon.warning
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -179,43 +186,28 @@ private fun Icon(state: TransactionState, modifier: Modifier = Modifier) {
|
|||
@Composable
|
||||
private fun Title(state: TransactionState, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is TransactionState.ProcessedTransactionContent -> {
|
||||
is TransactionState.Content -> {
|
||||
Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6)) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = when (state) {
|
||||
is TransactionState.Sending -> R.string.common_transfer
|
||||
is TransactionState.Receiving -> R.string.common_transfer
|
||||
is TransactionState.Approving -> R.string.common_approval
|
||||
is TransactionState.Swapping -> R.string.common_swap
|
||||
},
|
||||
),
|
||||
text = when (state) {
|
||||
is TransactionState.Approve -> stringResource(R.string.common_approval)
|
||||
is TransactionState.Transfer -> stringResource(R.string.common_transfer)
|
||||
is TransactionState.Swap -> stringResource(R.string.common_swap)
|
||||
is TransactionState.Custom -> state.title.resolveReference()
|
||||
},
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
|
||||
Image(
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
painter = painterResource(id = R.drawable.img_loader_15),
|
||||
contentDescription = null,
|
||||
)
|
||||
if (state.status is Status.Unconfirmed) {
|
||||
Image(
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
painter = painterResource(id = R.drawable.img_loader_15),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is TransactionState.CompletedTransactionContent -> {
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = when (state) {
|
||||
is TransactionState.Send -> R.string.common_transfer
|
||||
is TransactionState.Receive -> R.string.common_transfer
|
||||
is TransactionState.Approved -> R.string.common_approval
|
||||
is TransactionState.Swapped -> R.string.common_swap
|
||||
},
|
||||
),
|
||||
modifier = modifier,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
}
|
||||
is TransactionState.Loading -> {
|
||||
RectangleShimmer(
|
||||
modifier = modifier.size(width = TangemTheme.dimens.size70, height = TangemTheme.dimens.size12),
|
||||
|
|
@ -234,32 +226,42 @@ private fun Subtitle(state: TransactionState, modifier: Modifier = Modifier) {
|
|||
when (state) {
|
||||
is TransactionState.Content -> {
|
||||
Text(
|
||||
text = when (state) {
|
||||
is TransactionState.Sending,
|
||||
is TransactionState.Send,
|
||||
-> stringResource(
|
||||
id = R.string.transaction_history_transaction_to_address,
|
||||
state.address.resolveReference(),
|
||||
)
|
||||
is TransactionState.Receiving,
|
||||
is TransactionState.Receive,
|
||||
is TransactionState.Approving,
|
||||
is TransactionState.Approved,
|
||||
-> stringResource(
|
||||
id = R.string.transaction_history_transaction_from_address,
|
||||
state.address.resolveReference(),
|
||||
)
|
||||
is TransactionState.Swapping,
|
||||
is TransactionState.Swapped,
|
||||
-> stringResource(
|
||||
id = R.string.transaction_history_contract_address,
|
||||
state.address.resolveReference(),
|
||||
)
|
||||
text = when (state.status) {
|
||||
is Status.Failed -> stringResource(id = R.string.common_transaction_failed)
|
||||
else -> when (state) {
|
||||
is TransactionState.Transfer -> {
|
||||
when (state.direction) {
|
||||
Direction.OUTGOING -> stringResource(
|
||||
id = R.string.transaction_history_transaction_to_address,
|
||||
state.address.resolveReference(),
|
||||
)
|
||||
Direction.INCOMING -> stringResource(
|
||||
id = R.string.transaction_history_transaction_from_address,
|
||||
state.address.resolveReference(),
|
||||
)
|
||||
}
|
||||
}
|
||||
is TransactionState.Approve -> stringResource(
|
||||
id = R.string.transaction_history_transaction_from_address,
|
||||
state.address.resolveReference(),
|
||||
)
|
||||
is TransactionState.Swap -> stringResource(
|
||||
id = R.string.transaction_history_contract_address,
|
||||
state.address.resolveReference(),
|
||||
)
|
||||
is TransactionState.Custom -> stringResource(
|
||||
id = when (state.direction) {
|
||||
Direction.OUTGOING -> R.string.transaction_history_transaction_to_address
|
||||
Direction.INCOMING -> R.string.transaction_history_transaction_from_address
|
||||
},
|
||||
state.subtitle.resolveReference(),
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
textAlign = TextAlign.Start,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
is TransactionState.Loading -> {
|
||||
|
|
@ -276,14 +278,17 @@ private fun Subtitle(state: TransactionState, modifier: Modifier = Modifier) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun Amount(state: TransactionState, modifier: Modifier = Modifier) {
|
||||
private fun Amount(state: TransactionState, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is TransactionState.Content -> {
|
||||
Text(
|
||||
text = state.amount,
|
||||
text = if (isBalanceHidden) Strings.STARS else state.amount,
|
||||
modifier = modifier,
|
||||
textAlign = TextAlign.End,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
color = when (state.direction) {
|
||||
Direction.INCOMING -> TangemTheme.colors.text.accent
|
||||
Direction.OUTGOING -> TangemTheme.colors.text.primary1
|
||||
},
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
|
|
@ -309,7 +314,7 @@ private fun Timestamp(state: TransactionState, modifier: Modifier = Modifier) {
|
|||
modifier = modifier,
|
||||
textAlign = TextAlign.End,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
is TransactionState.Loading -> {
|
||||
|
|
@ -341,7 +346,7 @@ private fun Preview_TransactionItem_LightTheme(
|
|||
@PreviewParameter(TransactionItemStateProvider::class) state: TransactionState,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
Transaction(state)
|
||||
Transaction(state = state, isBalanceHidden = false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -351,59 +356,87 @@ private fun Preview_TransactionItem_DarkTheme(
|
|||
@PreviewParameter(TransactionItemStateProvider::class) state: TransactionState,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
Transaction(state)
|
||||
Transaction(state = state, isBalanceHidden = false)
|
||||
}
|
||||
}
|
||||
|
||||
private class TransactionItemStateProvider : CollectionPreviewParameterProvider<TransactionState>(
|
||||
collection = listOf(
|
||||
TransactionState.Sending(
|
||||
TransactionState.Transfer(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "-0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
status = Status.Confirmed,
|
||||
direction = Direction.OUTGOING,
|
||||
onClick = {},
|
||||
),
|
||||
TransactionState.Receiving(
|
||||
TransactionState.Transfer(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "+0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
status = Status.Unconfirmed,
|
||||
direction = Direction.INCOMING,
|
||||
onClick = {},
|
||||
),
|
||||
TransactionState.Approving(
|
||||
TransactionState.Approve(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "+0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
status = Status.Unconfirmed,
|
||||
direction = Direction.OUTGOING,
|
||||
onClick = {},
|
||||
),
|
||||
TransactionState.Swapping(
|
||||
TransactionState.Approve(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "+0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
status = Status.Failed,
|
||||
direction = Direction.OUTGOING,
|
||||
onClick = {},
|
||||
),
|
||||
TransactionState.Send(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "-0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
),
|
||||
TransactionState.Receive(
|
||||
TransactionState.Approve(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "+0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
status = Status.Confirmed,
|
||||
direction = Direction.OUTGOING,
|
||||
onClick = {},
|
||||
),
|
||||
TransactionState.Approved(
|
||||
TransactionState.Swap(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "+0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
status = Status.Unconfirmed,
|
||||
direction = Direction.INCOMING,
|
||||
onClick = {},
|
||||
),
|
||||
TransactionState.Swapped(
|
||||
TransactionState.Custom(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "+0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
status = Status.Confirmed,
|
||||
direction = Direction.INCOMING,
|
||||
title = TextReference.Str("Submit"),
|
||||
subtitle = TextReference.Str("33BddS...ga2B"),
|
||||
onClick = {},
|
||||
),
|
||||
TransactionState.Custom(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "+0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
status = Status.Confirmed,
|
||||
direction = Direction.OUTGOING,
|
||||
title = TextReference.Str("Submit"),
|
||||
subtitle = TextReference.Str("33BddS...ga2B"),
|
||||
onClick = {},
|
||||
),
|
||||
TransactionState.Loading(txHash = UUID.randomUUID().toString()),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.itemContentType
|
||||
|
|
@ -25,20 +24,19 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
fun LazyListScope.txHistoryItems(
|
||||
state: TxHistoryState,
|
||||
txHistoryItems: LazyPagingItems<TxHistoryState.TxHistoryItemState>?,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (state) {
|
||||
is TxHistoryState.Content -> {
|
||||
contentItems(
|
||||
txHistoryItems = requireNotNull(txHistoryItems),
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
is TxHistoryState.Empty -> {
|
||||
nonContentItem(
|
||||
state = EmptyTransactionsBlockState.Empty(onClick = state.onBuyClick),
|
||||
modifier = modifier,
|
||||
)
|
||||
nonContentItem(state = EmptyTransactionsBlockState.Empty, modifier = modifier)
|
||||
}
|
||||
is TxHistoryState.Error -> {
|
||||
nonContentItem(
|
||||
|
|
@ -58,38 +56,34 @@ fun LazyListScope.txHistoryItems(
|
|||
@OptIn(ExperimentalFoundationApi::class)
|
||||
private fun LazyListScope.contentItems(
|
||||
txHistoryItems: LazyPagingItems<TxHistoryState.TxHistoryItemState>,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
txHistoryItems.itemKey { item ->
|
||||
when (item) {
|
||||
is TxHistoryState.TxHistoryItemState.GroupTitle -> item.title
|
||||
is TxHistoryState.TxHistoryItemState.Title -> item.onExploreClick.hashCode()
|
||||
is TxHistoryState.TxHistoryItemState.Transaction -> item.state.txHash
|
||||
}
|
||||
}
|
||||
|
||||
txHistoryItems.itemContentType { it::class.java }
|
||||
|
||||
itemsIndexed(
|
||||
items = txHistoryItems.itemSnapshotList.items,
|
||||
key = { _, item ->
|
||||
items(
|
||||
count = txHistoryItems.itemCount,
|
||||
key = txHistoryItems.itemKey { item ->
|
||||
when (item) {
|
||||
is TxHistoryState.TxHistoryItemState.GroupTitle -> item.title
|
||||
is TxHistoryState.TxHistoryItemState.Title -> item.onExploreClick.hashCode()
|
||||
is TxHistoryState.TxHistoryItemState.Transaction -> item.state.txHash
|
||||
}
|
||||
},
|
||||
) { index, item ->
|
||||
TxHistoryListItem(
|
||||
state = item,
|
||||
modifier = modifier
|
||||
.animateItemPlacement()
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
lastIndex = txHistoryItems.itemSnapshotList.lastIndex,
|
||||
),
|
||||
)
|
||||
}
|
||||
contentType = txHistoryItems.itemContentType { it::class.java },
|
||||
itemContent = { index ->
|
||||
txHistoryItems[index]?.let { item ->
|
||||
TxHistoryListItem(
|
||||
state = item,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = modifier
|
||||
.animateItemPlacement()
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
lastIndex = txHistoryItems.itemSnapshotList.lastIndex,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue