Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci fixes develop to staging #1203

Open
wants to merge 20 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package jp.co.soramitsu.common.scan

import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.view.KeyEvent
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.LiveData
Expand All @@ -24,21 +24,16 @@ import jp.co.soramitsu.common.utils.EventObserver

@AndroidEntryPoint
class ScannerActivity : AppCompatActivity() {
companion object {
private const val QR_CODE_IMAGE_TYPE = "image/*"
}

@Inject
lateinit var viewModel: ScannerViewModel

private var capture: CaptureManager? = null

private val startForResultFromGallery: ActivityResultLauncher<Intent> =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.data?.let { selectedImageUri ->
viewModel.qrFileChosen(selectedImageUri)
}
private val startForResultFromGallery: ActivityResultLauncher<PickVisualMediaRequest> =
registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { resultUri: Uri? ->
resultUri?.let { selectedImageUri ->
viewModel.qrFileChosen(selectedImageUri)
}
}

Expand Down Expand Up @@ -89,11 +84,9 @@ class ScannerActivity : AppCompatActivity() {
}

private fun selectQrFromGallery() {
val intent = Intent().apply {
type = QR_CODE_IMAGE_TYPE
action = Intent.ACTION_GET_CONTENT
}
startForResultFromGallery.launch(intent)
val pickVisualMediaRequest =
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
startForResultFromGallery.launch(pickVisualMediaRequest)
}

inline fun <V> LiveData<Event<V>>.observeEvent(crossinline observer: (V) -> Unit) {
Expand Down
4 changes: 3 additions & 1 deletion core-db/src/main/java/jp/co/soramitsu/coredb/AppDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import jp.co.soramitsu.coredb.migrations.Migration_66_67
import jp.co.soramitsu.coredb.migrations.Migration_67_68
import jp.co.soramitsu.coredb.migrations.Migration_68_69
import jp.co.soramitsu.coredb.migrations.Migration_69_70
import jp.co.soramitsu.coredb.migrations.Migration_70_71
import jp.co.soramitsu.coredb.migrations.RemoveAccountForeignKeyFromAsset_17_18
import jp.co.soramitsu.coredb.migrations.RemoveLegacyData_35_36
import jp.co.soramitsu.coredb.migrations.RemoveStakingRewardsTable_22_23
Expand Down Expand Up @@ -103,7 +104,7 @@ import jp.co.soramitsu.coredb.model.chain.FavoriteChainLocal
import jp.co.soramitsu.coredb.model.chain.MetaAccountLocal

@Database(
version = 70,
version = 71,
entities = [
AccountLocal::class,
AddressBookContact::class,
Expand Down Expand Up @@ -197,6 +198,7 @@ abstract class AppDatabase : RoomDatabase() {
.addMigrations(Migration_67_68)
.addMigrations(Migration_68_69)
.addMigrations(Migration_69_70)
.addMigrations(Migration_70_71)
.build()
}
return instance!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ package jp.co.soramitsu.coredb.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase

val Migration_70_71 = object : Migration(70, 71) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE chains ADD COLUMN `remoteAssetsSource` TEXT NULL DEFAULT NULL")
}
}

val Migration_69_70 = object : Migration(69, 70) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("DELETE FROM storage")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ data class ChainLocal(
val isChainlinkProvider: Boolean,
val supportNft: Boolean,
val isUsesAppId: Boolean,
val identityChain: String?
val identityChain: String?,
val remoteAssetsSource: String?
) {

class ExternalApi(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package jp.co.soramitsu.wallet.impl.data.repository

import android.annotation.SuppressLint
import jp.co.soramitsu.common.data.network.coingecko.CoingeckoApi
import jp.co.soramitsu.common.data.network.coingecko.FiatCurrency
import jp.co.soramitsu.common.data.network.runtime.binding.cast
import jp.co.soramitsu.common.domain.GetAvailableFiatCurrencies
import jp.co.soramitsu.common.domain.SelectedFiat
import jp.co.soramitsu.core.models.Asset.PriceProvider
import jp.co.soramitsu.core.models.Asset.PriceProviderType
import jp.co.soramitsu.coredb.dao.TokenPriceDao
import jp.co.soramitsu.coredb.model.TokenPriceLocal
import jp.co.soramitsu.runtime.multiNetwork.chain.ChainsRepository
import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId
import jp.co.soramitsu.wallet.impl.data.network.blockchain.EthereumRemoteSource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.job
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.math.BigDecimal

class PricesSyncService(
private val tokenPriceDao: TokenPriceDao,
private val coingeckoPricesService: CoingeckoPricesService,
private val chainlinkPricesService: ChainlinkPricesService,
private val selectedFiat: SelectedFiat,
private val availableFiatCurrencies: GetAvailableFiatCurrencies,
) {

private val syncMutex = Mutex()
private var pricesSyncJob: Job? = null

@SuppressLint("LogNotTimber")
suspend fun sync() = withContext(Dispatchers.Default) {

Check warning on line 40 in feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/PricesSyncService.kt

View check run for this annotation

Soramitsu-Sonar-PR-decoration / fearless-android Sonarqube Results

feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/PricesSyncService.kt#L40

Avoid hardcoded dispatchers.
syncMutex.withLock {
if (pricesSyncJob?.isCompleted == false && pricesSyncJob?.isCancelled == false) return@withContext
pricesSyncJob?.cancel()
pricesSyncJob = coroutineScope {
val selectedFiat = selectedFiat.get()
val fiatModel = availableFiatCurrencies[selectedFiat]
?: return@coroutineScope this.coroutineContext.job

val coingeckoPrices = async { coingeckoPricesService.load(fiatModel) }

val chainlinkPricesDeferred = async {
val price24hChange =
coingeckoPrices.await().associate { it.priceId to it.recentRateChange }

.filterValues { it != null }.cast<Map<String, BigDecimal>>()
chainlinkPricesService.load(fiatModel, price24hChange)
}

val allPrices = coingeckoPrices.await() + chainlinkPricesDeferred.await()
tokenPriceDao.insertTokensPrice(allPrices)

this.coroutineContext.job
}
}
}
}

// priceId to [
// currencyId to priceValue,
// currencyId_24h_change to changeValue
// ]
typealias CoingeckoResponse = Map<String, Map<String, BigDecimal>>

class CoingeckoPricesService(
private val coingeckoApi: CoingeckoApi,
private val chainsRepository: ChainsRepository
) {
companion object {
private const val COINGECKO_REQUEST_DELAY_MILLIS = 60 * 1000
}
private val cache = mutableMapOf<String, CoingeckoResponse>()
private val timestamps = mutableMapOf<String, Long>()

suspend fun load(currency: FiatCurrency): List<TokenPriceLocal> {
val allAssets = chainsRepository.getChains().map { it.assets }.flatten()
val priceIds = allAssets.mapNotNull { it.priceId }

val cacheAlive = System.currentTimeMillis() < (timestamps.getOrDefault(currency.id, 0L) + COINGECKO_REQUEST_DELAY_MILLIS)
val cacheExists = cache[currency.id] != null
val shouldGetFromCache = cacheExists && cacheAlive

val coingeckoPriceStats = if(shouldGetFromCache) {
requireNotNull(cache[currency.id])
} else {
coingeckoApi.getAssetPrice(priceIds.joinToString(","), currency.id, true)
.also {
timestamps[currency.id] = System.currentTimeMillis()
cache[currency.id] = it
}
}

val tokenPrices = priceIds.mapNotNull { priceId ->
val stat = coingeckoPriceStats[priceId] ?: return@mapNotNull null

val changeKey = "${currency.id}_24h_change"
val change = stat[changeKey]

TokenPriceLocal(priceId, stat[currency.id], currency.symbol, change)
}

return tokenPrices
}
}

class ChainlinkPricesService(
private val ethereumSource: EthereumRemoteSource,
private val chainsRepository: ChainsRepository
) {
suspend fun load(
currency: FiatCurrency,
prices24hChange: Map<String, BigDecimal>
): List<TokenPriceLocal> {
if (currency.id != "usd") {
return emptyList()
}
val chains = chainsRepository.getChains()
val chainlinkServiceProvider = chains.find { it.chainlinkProvider } ?: return emptyList()
val chainlinkAssets = chains.map { it.assets }.flatten()
.filter { it.priceProvider?.type == PriceProviderType.Chainlink }

return supervisorScope {
chainlinkAssets.map { asset ->
async {
val priceProvider = asset.priceProvider ?: return@async null
val price =
getChainlinkPrices(
priceProvider = priceProvider,
chainId = chainlinkServiceProvider.id
)
?: return@async null

TokenPriceLocal(
priceProvider.id,
price,
currency.symbol,
prices24hChange.getOrDefault(asset.priceId, null)
)
}
}.awaitAll().filterNotNull()
}
}

private suspend fun getChainlinkPrices(
priceProvider: PriceProvider,
chainId: ChainId
): BigDecimal? {
return runCatching {
ethereumSource.fetchPriceFeed(
chainId = chainId,
receiverAddress = priceProvider.id
)?.let { price ->
BigDecimal(price, priceProvider.precision)
}
}.getOrNull()
}
}
Loading