Updated on 2026-08-14
This commit is contained in:
commit
2da48c037a
961 changed files with 24212 additions and 13399 deletions
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue