diff --git a/common/src/main/java/org/dash/wallet/common/ui/radio_group/RadioGroupAdapter.kt b/common/src/main/java/org/dash/wallet/common/ui/radio_group/RadioGroupAdapter.kt
index 83dc3fbb84..c7687b9861 100644
--- a/common/src/main/java/org/dash/wallet/common/ui/radio_group/RadioGroupAdapter.kt
+++ b/common/src/main/java/org/dash/wallet/common/ui/radio_group/RadioGroupAdapter.kt
@@ -83,7 +83,7 @@ class RadioGroupAdapter(
}
}
-class RadioButtonViewHolder(
+open class RadioButtonViewHolder(
val binding: RadiobuttonRowBinding,
private val isCheckMark: Boolean
) : RecyclerView.ViewHolder(binding.root) {
diff --git a/common/src/main/java/org/dash/wallet/common/ui/recyclerview/IconifiedListAdapter.kt b/common/src/main/java/org/dash/wallet/common/ui/recyclerview/IconifiedListAdapter.kt
new file mode 100644
index 0000000000..5871997ee6
--- /dev/null
+++ b/common/src/main/java/org/dash/wallet/common/ui/recyclerview/IconifiedListAdapter.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2021 Dash Core Group.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.dash.wallet.common.ui.recyclerview
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.ListAdapter
+import org.dash.wallet.common.R
+import org.dash.wallet.common.databinding.RadiobuttonRowBinding
+import org.dash.wallet.common.ui.radio_group.IconifiedViewItem
+import org.dash.wallet.common.ui.radio_group.RadioButtonViewHolder
+import org.dash.wallet.common.ui.radio_group.RadioGroupAdapter
+import org.dash.wallet.common.ui.setRoundedRippleBackground
+
+class IconifiedListAdapter(
+ private val clickListener: (IconifiedViewItem, Int) -> Unit
+): ListAdapter(RadioGroupAdapter.DiffCallback()) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IconifiedViewHolder {
+ val inflater = LayoutInflater.from(parent.context)
+ val binding = RadiobuttonRowBinding.inflate(inflater, parent, false)
+
+ return IconifiedViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: IconifiedViewHolder, position: Int) {
+ holder.itemView.isSelected = false
+ val item = getItem(position)
+ holder.bind(item)
+
+ holder.binding.root.setOnClickListener {
+ clickListener.invoke(item, position)
+ }
+ }
+}
+
+class IconifiedViewHolder(
+ binding: RadiobuttonRowBinding
+) : RadioButtonViewHolder(binding, false) {
+ fun bind(option: IconifiedViewItem) {
+ super.bind(option, false)
+ binding.checkmarkFrame.isVisible = false
+ itemView.setRoundedRippleBackground(R.style.ListViewButtonBackground)
+ }
+}
diff --git a/common/src/main/res/layout/iconifieditem_row.xml b/common/src/main/res/layout/iconifieditem_row.xml
new file mode 100644
index 0000000000..1078095399
--- /dev/null
+++ b/common/src/main/res/layout/iconifieditem_row.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/ApiStatuses.kt b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/ApiStatuses.kt
new file mode 100644
index 0000000000..faa35386f5
--- /dev/null
+++ b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/ApiStatuses.kt
@@ -0,0 +1,7 @@
+package org.dash.wallet.integrations.maya.api
+
+open class MayaException(message: String): Exception(message) {
+ companion object {
+ const val SWAP_ERROR = "deposit_error"
+ }
+}
\ No newline at end of file
diff --git a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/ExchangeRateApi.kt b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/ExchangeRateApi.kt
new file mode 100644
index 0000000000..efaec5dcac
--- /dev/null
+++ b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/ExchangeRateApi.kt
@@ -0,0 +1,11 @@
+package org.dash.wallet.integrations.maya.api
+
+import org.dash.wallet.integrations.maya.model.ExchangeRateResponse
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface ExchangeRateApi {
+ @GET("latest")
+ suspend fun getRates(@Query("base") baseCurrencyCode: String): Response
+}
diff --git a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/FiatExchangeRateApi.kt b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/FiatExchangeRateApi.kt
new file mode 100644
index 0000000000..721d16cf09
--- /dev/null
+++ b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/FiatExchangeRateApi.kt
@@ -0,0 +1,85 @@
+package org.dash.wallet.integrations.maya.api
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+import org.dash.wallet.common.data.entity.ExchangeRate
+import org.dash.wallet.integrations.maya.utils.MayaConstants
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+class FiatExchangeRateApiAggregator @Inject constructor(
+ val exchangeRateApi: ExchangeRateApi
+) {
+ companion object {
+ val log: Logger = LoggerFactory.getLogger(FiatExchangeRateApiAggregator::class.java)
+ }
+ suspend fun getRate(currencyCode: String): ExchangeRate? {
+ val response = exchangeRateApi.getRates(MayaConstants.DEFAULT_EXCHANGE_CURRENCY).body()
+ val exchangeRate = response?.rates?.get(currencyCode) ?: 0.0
+ log.info("exchange rate: {} {}", exchangeRate, currencyCode)
+ return if (exchangeRate != 0.0) {
+ ExchangeRate(currencyCode, exchangeRate.toString())
+ } else {
+ null
+ }
+ }
+}
+
+interface FiatExchangeRateProvider {
+ val fiatExchangeRate: Flow
+ fun observeFiatRates(): Flow>
+ fun observeFiatRate(currencyCode: String): Flow
+}
+
+class FiatExchangeRateAggregatedProvider @Inject constructor(
+ val fiatExchangeRateApi: FiatExchangeRateApiAggregator
+) : FiatExchangeRateProvider {
+ companion object {
+ private val log = LoggerFactory.getLogger(FiatExchangeRateApiAggregator::class.java)
+ private val UPDATE_FREQ_MS = TimeUnit.SECONDS.toMillis(30)
+ }
+
+ private val responseScope = CoroutineScope(
+ Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+ )
+ private var poolListLastUpdated: Long = 0
+ override val fiatExchangeRate = MutableStateFlow(ExchangeRate(MayaConstants.DEFAULT_EXCHANGE_CURRENCY, "1.0"))
+ override fun observeFiatRates(): Flow> {
+ TODO("Not yet implemented")
+ }
+
+ override fun observeFiatRate(currencyCode: String): Flow {
+ if (shouldRefresh()) {
+ refreshRates(currencyCode)
+ }
+ return fiatExchangeRate
+ }
+
+ private fun refreshRates(currencyCode: String) {
+ if (!shouldRefresh()) {
+ return
+ }
+
+ responseScope.launch {
+ updateExchangeRates(currencyCode)
+ poolListLastUpdated = System.currentTimeMillis()
+ }
+ }
+
+ private fun shouldRefresh(): Boolean {
+ val now = System.currentTimeMillis()
+ return poolListLastUpdated == 0L || now - poolListLastUpdated > UPDATE_FREQ_MS
+ }
+
+ suspend fun updateExchangeRates(currencyCode: String) {
+ fiatExchangeRateApi.getRate(currencyCode)?.let { rate ->
+ fiatExchangeRate.value = rate
+ }
+ }
+}
diff --git a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/MayaApi.kt b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/MayaApi.kt
new file mode 100644
index 0000000000..ae65436934
--- /dev/null
+++ b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/MayaApi.kt
@@ -0,0 +1,188 @@
+package org.dash.wallet.integrations.maya.api
+
+import android.content.Context
+import android.content.Intent
+import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.bitcoinj.utils.Fiat
+import org.dash.wallet.common.Configuration
+import org.dash.wallet.common.WalletDataProvider
+import org.dash.wallet.common.services.AuthenticationManager
+import org.dash.wallet.common.services.NotificationService
+import org.dash.wallet.common.services.TransactionMetadataProvider
+import org.dash.wallet.common.services.analytics.AnalyticsService
+import org.dash.wallet.integrations.maya.MayaWebApi
+import org.dash.wallet.integrations.maya.model.PoolInfo
+import org.dash.wallet.integrations.maya.utils.MayaConfig
+import org.dash.wallet.integrations.maya.utils.MayaConstants
+import org.slf4j.LoggerFactory
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+interface MayaApi {
+ val poolInfoList: MutableStateFlow>
+ val apiError: MutableStateFlow
+ var notificationIntent: Intent?
+ var showNotificationOnResult: Boolean
+
+ suspend fun swap()
+ suspend fun reset()
+ //fun observePoolList(): Flow>
+ fun observePoolList(fiatExchangeRate: Fiat): Flow>
+}
+
+class MayaApiAggregator @Inject constructor(
+ private val webApi: MayaWebApi,
+ private val blockchainApi: MayaBlockchainApi,
+ private val walletDataProvider: WalletDataProvider,
+ private val notificationService: NotificationService,
+ private val analyticsService: AnalyticsService,
+ private val config: MayaConfig,
+ private val globalConfig: Configuration,
+ private val securityFunctions: AuthenticationManager,
+ private val transactionMetadataProvider: TransactionMetadataProvider,
+ @ApplicationContext private val appContext: Context
+): MayaApi {
+ companion object {
+ private val log = LoggerFactory.getLogger(MayaApiAggregator::class.java)
+ private val UPDATE_FREQ_MS = TimeUnit.SECONDS.toMillis(30)
+ private const val CONFIRMED_STATUS = "confirmed"
+ private const val VALID_STATUS = "valid"
+ private const val MESSAGE_RECEIVED_STATUS = "received"
+ private const val MESSAGE_FAILED_STATUS = "failed"
+ }
+
+ private val params = walletDataProvider.networkParameters
+ private var tickerJob: Job? = null
+ private val configScope = CoroutineScope(Dispatchers.IO)
+ private val responseScope = CoroutineScope(
+ Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+ )
+ private val statusScope = CoroutineScope(
+ Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+ )
+ private var poolListLastUpdated: Long = 0
+ override val poolInfoList = MutableStateFlow>(listOf())
+
+ override val apiError = MutableStateFlow(null)
+ override var notificationIntent: Intent? = null
+ override var showNotificationOnResult = false
+
+ override suspend fun swap() {
+ TODO("Not yet implemented")
+ }
+
+ init {
+ walletDataProvider.attachOnWalletWipedListener {
+ configScope.launch { reset() }
+ }
+
+ config.observe(MayaConfig.BACKGROUND_ERROR)
+ .filterNot { it.isNullOrEmpty() }
+ .onEach {
+ if (apiError.value == null) {
+ apiError.value = MayaException(it ?: "")
+ config.set(MayaConfig.BACKGROUND_ERROR, "")
+ }
+ }
+ .launchIn(configScope)
+ }
+
+ private suspend fun updatePoolList(fiatExchangeRate: Fiat) {
+ val resultWithUSDRates = webApi.getPoolInfo()
+ val resultWithFiatRates = resultWithUSDRates.map { pool ->
+ log.info("adjusting fiat value {}: {}", pool.asset, pool)
+ log.info(" {}", fiatExchangeRate.toFriendlyString())
+ pool.setAssetPrice(fiatExchangeRate)
+ log.info(" {}", pool.assetPriceFiat.toFriendlyString())
+ pool
+ }
+ log.info("USD: {}", resultWithUSDRates.map { it.assetPriceFiat })
+ log.info("Fiat: {}", resultWithFiatRates.map { it.assetPriceFiat })
+ poolInfoList.value = resultWithFiatRates
+ }
+
+ override suspend fun reset() {
+ log.info("reset is triggered")
+ poolInfoList.value = listOf()
+ apiError.value = null
+ }
+
+ private fun isError(): Boolean {
+ val savedError = runBlocking { config.get(MayaConfig.BACKGROUND_ERROR) ?: "" }
+
+ if (savedError.isNotEmpty()) {
+ apiError.value = MayaException(savedError)
+ configScope.launch { config.set(MayaConfig.BACKGROUND_ERROR, "") }
+ log.info("found an error: $savedError")
+ return true
+ }
+
+ return false
+ }
+
+ private fun cancelTrackingJob() {
+ tickerJob?.cancel()
+ tickerJob = null
+ }
+
+ private fun handleError(ex: Exception, error: String) {
+ apiError.value = ex
+ notifyIfNeeded(error, "maya_error")
+ log.error("$error: $ex")
+ analyticsService.logError(ex)
+ }
+
+ private fun notifyIfNeeded(message: String, tag: String) {
+ if (showNotificationOnResult) {
+ notificationService.showNotification(
+ tag,
+ message,
+ intent = notificationIntent
+ )
+ }
+ }
+
+// override fun observePoolList(): Flow> {
+// if (shouldRefresh()) {
+// refreshRates(Fiat.valueOf(MayaConstants.DEFAULT_EXCHANGE_CURRENCY, 100000000))
+// }
+// return poolInfoList
+// }
+
+ override fun observePoolList(fiatExchangeRate: Fiat): Flow> {
+ log.info("observePoolList(${fiatExchangeRate.toFriendlyString()})")
+ if (shouldRefresh()) {
+ refreshRates(fiatExchangeRate)
+ }
+ return poolInfoList
+ }
+
+ private fun refreshRates(fiatExchangeRate: Fiat) {
+ log.info("refreshRates(${fiatExchangeRate.toFriendlyString()})")
+ if (!shouldRefresh()) {
+ return
+ }
+
+ responseScope.launch {
+ updatePoolList(fiatExchangeRate)
+ poolListLastUpdated = System.currentTimeMillis()
+ }
+ }
+
+ private fun shouldRefresh(): Boolean {
+ val now = System.currentTimeMillis()
+ return poolListLastUpdated == 0L || now - poolListLastUpdated > UPDATE_FREQ_MS
+ }
+}
diff --git a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/MayaBlockchainApi.kt b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/MayaBlockchainApi.kt
new file mode 100644
index 0000000000..dc77daec90
--- /dev/null
+++ b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/MayaBlockchainApi.kt
@@ -0,0 +1,8 @@
+package org.dash.wallet.integrations.maya.api
+
+import javax.inject.Inject
+
+class MayaBlockchainApi @Inject constructor() {
+ suspend fun swap() {
+ }
+}
diff --git a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/MayaWebApi.kt b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/MayaWebApi.kt
new file mode 100644
index 0000000000..7b0bc30590
--- /dev/null
+++ b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/MayaWebApi.kt
@@ -0,0 +1,45 @@
+package org.dash.wallet.integrations.maya
+
+import org.dash.wallet.common.services.analytics.AnalyticsService
+import org.dash.wallet.integrations.maya.model.PoolInfo
+import org.slf4j.LoggerFactory
+import retrofit2.Response
+import retrofit2.http.GET
+import java.io.IOException
+import javax.inject.Inject
+
+interface MayaEndpoint {
+ @GET("pools")
+ suspend fun getPoolInfo(): Response>
+}
+
+open class MayaWebApi @Inject constructor(
+ private val endpoint: MayaEndpoint,
+ private val analyticsService: AnalyticsService
+) {
+ companion object {
+ private val log = LoggerFactory.getLogger(MayaWebApi::class.java)
+ }
+
+ suspend fun getPoolInfo(): List {
+ return try {
+ val response = endpoint.getPoolInfo()
+ log.info("maya: response: {}", response)
+
+ return if (response.isSuccessful && response.body()?.isNotEmpty() == true) {
+ response.body()!!.toList()
+ } else {
+ log.error("getWithdrawalLimits not successful; ${response.code()} : ${response.message()}")
+ listOf()
+ }
+ } catch (ex: Exception) {
+ log.error("Error in getPoolInfo: $ex")
+
+ if (ex !is IOException) {
+ analyticsService.logError(ex)
+ }
+
+ listOf()
+ }
+ }
+}
diff --git a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/RemoteDataSource.kt b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/RemoteDataSource.kt
new file mode 100644
index 0000000000..eb8501b851
--- /dev/null
+++ b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/RemoteDataSource.kt
@@ -0,0 +1,33 @@
+package org.dash.wallet.integrations.maya.api
+
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import org.dash.wallet.common.BuildConfig
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import javax.inject.Inject
+
+class RemoteDataSource @Inject constructor() {
+ fun buildApi(
+ api: Class,
+ baseUrl: String
+ ): Api {
+ return Retrofit.Builder()
+ .baseUrl(baseUrl)
+ .client(getRetrofitClient())
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(api)
+ }
+
+ private fun getRetrofitClient(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .also { client ->
+ if (BuildConfig.DEBUG) {
+ val logging = HttpLoggingInterceptor()
+ logging.level = HttpLoggingInterceptor.Level.BODY
+ client.addInterceptor(logging)
+ }
+ }.build()
+ }
+}
\ No newline at end of file
diff --git a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/data/CryptoCurrencyItem.kt b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/data/CryptoCurrencyItem.kt
new file mode 100644
index 0000000000..cb346bd4b1
--- /dev/null
+++ b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/data/CryptoCurrencyItem.kt
@@ -0,0 +1,3 @@
+package org.dash.wallet.integrations.maya.data
+
+data class CryptoCurrencyItem(val currency: String)
diff --git a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/di/MayaModule.kt b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/di/MayaModule.kt
new file mode 100644
index 0000000000..ab50c316dd
--- /dev/null
+++ b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/di/MayaModule.kt
@@ -0,0 +1,54 @@
+package org.dash.wallet.integrations.maya.di
+
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.FlowPreview
+import org.dash.wallet.common.WalletDataProvider
+import org.dash.wallet.integrations.maya.MayaEndpoint
+import org.dash.wallet.integrations.maya.api.ExchangeRateApi
+import org.dash.wallet.integrations.maya.api.FiatExchangeRateAggregatedProvider
+import org.dash.wallet.integrations.maya.api.FiatExchangeRateProvider
+import org.dash.wallet.integrations.maya.api.MayaApi
+import org.dash.wallet.integrations.maya.api.MayaApiAggregator
+import org.dash.wallet.integrations.maya.api.RemoteDataSource
+import org.dash.wallet.integrations.maya.utils.MayaConstants
+import javax.inject.Singleton
+import kotlin.time.ExperimentalTime
+
+@Module
+@InstallIn(SingletonComponent::class)
+@ExperimentalCoroutinesApi
+@ExperimentalTime
+@FlowPreview
+abstract class MayaModule {
+ companion object {
+ @Provides
+ fun provideMayaEndpoint(
+ remoteDataSource: RemoteDataSource,
+ walletDataProvider: WalletDataProvider
+ ): MayaEndpoint {
+ val baseUrl = MayaConstants.getBaseUrl(walletDataProvider.networkParameters)
+ return remoteDataSource.buildApi(MayaEndpoint::class.java, baseUrl)
+ }
+ @Provides
+ fun provideExchangeRateEndpoint(
+ remoteDataSource: RemoteDataSource,
+ walletDataProvider: WalletDataProvider
+ ): ExchangeRateApi {
+ val baseUrl = MayaConstants.EXCHANGERATE_BASE_URL
+ return remoteDataSource.buildApi(ExchangeRateApi::class.java, baseUrl)
+ }
+ }
+
+ @Binds
+ @Singleton
+ abstract fun bindMayaApi(mayaApi: MayaApiAggregator): MayaApi
+
+ @Binds
+ @Singleton
+ abstract fun bindFiatExchangeRateApi(fiatApi: FiatExchangeRateAggregatedProvider): FiatExchangeRateProvider
+}
\ No newline at end of file
diff --git a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/model/ExchangeRateResponse.kt b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/model/ExchangeRateResponse.kt
new file mode 100644
index 0000000000..494a474fb7
--- /dev/null
+++ b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/model/ExchangeRateResponse.kt
@@ -0,0 +1,14 @@
+package org.dash.wallet.integrations.maya.model
+
+data class ExchangeRateResponse(
+ val motd: Motd,
+ val success: Boolean,
+ val base: String,
+ val date: String,
+ val rates: Map
+)
+
+data class Motd(
+ val msg: String,
+ val url: String
+)
diff --git a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/model/PoolInfo.kt b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/model/PoolInfo.kt
new file mode 100644
index 0000000000..0ab820f966
--- /dev/null
+++ b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/model/PoolInfo.kt
@@ -0,0 +1,42 @@
+package org.dash.wallet.integrations.maya.model
+
+import org.bitcoinj.utils.Fiat
+import org.dash.wallet.common.util.toBigDecimal
+import org.dash.wallet.common.util.toFiat
+import org.dash.wallet.integrations.maya.utils.MayaConstants
+import java.math.BigDecimal
+
+data class PoolInfo(
+ val annualPercentageRate: String,
+ val asset: String,
+ val assetDepth: String,
+ val assetPrice: String,
+ val assetPriceUSD: String,
+ val liquidityUnits: String,
+ val poolAPY: String,
+ val runeDepth: String,
+ val status: String,
+ val synthSupply: String,
+ val synthUnits: String,
+ val units: String,
+ val volume24h: String
+) {
+ var assetPriceFiat: Fiat = Fiat.valueOf(MayaConstants.DEFAULT_EXCHANGE_CURRENCY, 0)
+
+ fun getAssetPriceUSD() : Fiat {
+ return Fiat.parseFiatInexact("USD", assetPriceUSD)
+ }
+
+ fun setAssetPrice(fiatExchangeRate: Fiat) {
+ assetPriceFiat = BigDecimal(assetPriceUSD)
+ .multiply(fiatExchangeRate.toBigDecimal())
+ .toFiat(fiatExchangeRate.currencyCode)
+ }
+
+ val currencyCode: String
+ get() {
+ val codeIndex = asset.indexOf('.')
+ val smartContract = asset.indexOf('-')
+ return asset.substring(codeIndex + 1, if (smartContract != -1) smartContract else asset.length)
+ }
+}
diff --git a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaCryptoCurrencyPickerFragment.kt b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaCryptoCurrencyPickerFragment.kt
new file mode 100644
index 0000000000..1138fe40de
--- /dev/null
+++ b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaCryptoCurrencyPickerFragment.kt
@@ -0,0 +1,119 @@
+package org.dash.wallet.integrations.maya.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
+import org.dash.wallet.common.ui.decorators.ListDividerDecorator
+import org.dash.wallet.common.ui.dialogs.AdaptiveDialog
+import org.dash.wallet.common.ui.radio_group.IconSelectMode
+import org.dash.wallet.common.ui.radio_group.IconifiedViewItem
+import org.dash.wallet.common.ui.recyclerview.IconifiedListAdapter
+import org.dash.wallet.common.ui.viewBinding
+import org.dash.wallet.common.util.GenericUtils
+import org.dash.wallet.common.util.observe
+import org.dash.wallet.integrations.maya.R
+import org.dash.wallet.integrations.maya.databinding.FragmentCurrencyPickerBinding
+import org.dash.wallet.integrations.maya.model.PoolInfo
+
+@AndroidEntryPoint
+class MayaCryptoCurrencyPickerFragment : Fragment(R.layout.fragment_currency_picker) {
+ private val binding by viewBinding(FragmentCurrencyPickerBinding::bind)
+ private val viewModel by viewModels()
+ private var itemList = listOf()
+ private lateinit var defaultItemMap: Map
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.toolbar.setNavigationOnClickListener {
+ findNavController().popBackStack()
+ }
+
+ val adapter = IconifiedListAdapter() { item, index ->
+ viewModel.poolList.value.firstOrNull {
+ it.currencyCode == item.title
+ }?.let {
+ clickListener(it)
+ }
+ }
+
+ // val adapter1 = ListAdapter(RadioGroupAdapter.DiffCallback())
+
+ val divider = ContextCompat.getDrawable(requireContext(), org.dash.wallet.common.R.drawable.list_divider)!!
+ val decorator = ListDividerDecorator(
+ divider,
+ showAfterLast = false,
+ marginStart = resources.getDimensionPixelOffset(org.dash.wallet.common.R.dimen.divider_margin_horizontal),
+ marginEnd = resources.getDimensionPixelOffset(org.dash.wallet.common.R.dimen.divider_margin_horizontal)
+ )
+ binding.contentList.addItemDecoration(decorator)
+ binding.contentList.adapter = adapter
+
+ defaultItemMap = mapOf(
+ "BTC.BTC" to IconifiedViewItem(
+ requireContext().getString(R.string.cryptocurrency_bitcoin_code),
+ requireContext().getString(R.string.cryptocurrency_bitcoin_network)
+ // R.drawable.ic_btc_logo
+ ),
+ "ETH.ETH" to IconifiedViewItem(
+ requireContext().getString(R.string.cryptocurrency_ethereum_code),
+ requireContext().getString(R.string.cryptocurrency_ethereum_network)
+ // R.drawable.ic_eth_logo
+ ),
+ "DASH.DASH" to IconifiedViewItem(
+ requireContext().getString(R.string.cryptocurrency_dash_code),
+ requireContext().getString(R.string.cryptocurrency_dash_network)
+ // R.drawable.ic_dash_d_circle
+ ),
+ "ETH.USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48" to IconifiedViewItem(
+ requireContext().getString(R.string.cryptocurrency_usdcoin_code),
+ requireContext().getString(R.string.cryptocurrency_usdcoin_network)
+ // R.drawable.blue_circle
+ ),
+ "ETH.USDT-0XDAC17F958D2EE523A2206206994597C13D831EC7" to IconifiedViewItem(
+ requireContext().getString(R.string.cryptocurrency_tether_code),
+ requireContext().getString(R.string.cryptocurrency_tether_network)
+ // R.drawable.blue_circle
+ ),
+ "THOR.RUNE" to IconifiedViewItem(
+ requireContext().getString(R.string.cryptocurrency_rune_code),
+ requireContext().getString(R.string.cryptocurrency_rune_network)
+ // R.drawable.ic_dash_d_circle
+ )
+ )
+
+ viewModel.poolList.observe(viewLifecycleOwner) {
+ lifecycleScope.launch {
+ itemList = it.filter { pool -> pool.asset != "DASH.DASH" }
+ .map { pool ->
+ if (defaultItemMap.containsKey(pool.asset)) {
+ val item = defaultItemMap[pool.asset]!!.copy(
+ iconUrl = "https://raw.githubusercontent.com/jsupa/crypto-icons/main/icons/" +
+ "${pool.currencyCode.lowercase()}.png",
+ iconSelectMode = IconSelectMode.None,
+ additionalInfo = GenericUtils.formatFiatWithoutComma(
+ viewModel.formatFiat(pool.assetPriceFiat)
+ )
+ )
+ println(item.iconUrl + " " + item.iconUrl)
+ item
+ } else {
+ IconifiedViewItem(pool.currencyCode, pool.asset)
+ }
+ }.sortedBy { it.title }
+ adapter.submitList(itemList)
+ }
+ }
+ }
+
+ fun clickListener(pool: PoolInfo) {
+ AdaptiveDialog.simple("${pool.currencyCode} was chosen", "Close").show(requireActivity()) {
+ }
+ }
+}
diff --git a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaPortalFragment.kt b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaPortalFragment.kt
index 30be0eaefa..e945f601c9 100644
--- a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaPortalFragment.kt
+++ b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaPortalFragment.kt
@@ -23,12 +23,11 @@ import android.view.View
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
-import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.launch
import org.dash.wallet.common.databinding.FragmentIntegrationPortalBinding
import org.dash.wallet.common.ui.viewBinding
+import org.dash.wallet.common.util.safeNavigate
import org.dash.wallet.integrations.maya.R
@AndroidEntryPoint
@@ -42,7 +41,7 @@ class MayaPortalFragment : Fragment(R.layout.fragment_integration_portal) {
super.onCreate(savedInstanceState)
binding.balanceDash.isVisible = false
- binding.balanceDash.setFormat(viewModel.balanceFormat)
+ binding.balanceDash.setFormat(viewModel.fiatFormat)
binding.balanceDash.setApplyMarkup(false)
binding.toolbarTitle.text = getString(R.string.maya_service_name)
@@ -72,6 +71,7 @@ class MayaPortalFragment : Fragment(R.layout.fragment_integration_portal) {
binding.convertBtn.setOnClickListener {
// TODO: add handler code here
+ safeNavigate(MayaPortalFragmentDirections.mayaPortalToCurrencyPicker())
}
}
}
diff --git a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaViewModel.kt b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaViewModel.kt
index a121fad76d..99caf8fbc1 100644
--- a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaViewModel.kt
+++ b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaViewModel.kt
@@ -21,44 +21,112 @@ import androidx.lifecycle.*
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
+import org.bitcoinj.core.Coin
+import org.bitcoinj.utils.Fiat
import org.bitcoinj.utils.MonetaryFormat
import org.dash.wallet.common.Configuration
-import org.dash.wallet.common.WalletDataProvider
import org.dash.wallet.common.data.SingleLiveEvent
import org.dash.wallet.common.data.WalletUIConfig
import org.dash.wallet.common.data.entity.ExchangeRate
import org.dash.wallet.common.services.ExchangeRatesProvider
import org.dash.wallet.common.services.analytics.AnalyticsService
+import org.dash.wallet.common.util.GenericUtils
+import org.dash.wallet.common.util.isCurrencyFirst
+import org.dash.wallet.integrations.maya.api.FiatExchangeRateProvider
+import org.dash.wallet.integrations.maya.api.MayaApi
+import org.dash.wallet.integrations.maya.data.CryptoCurrencyItem
+import org.dash.wallet.integrations.maya.model.PoolInfo
import org.dash.wallet.integrations.maya.utils.MayaConfig
+import org.slf4j.LoggerFactory
+import java.util.Locale
import javax.inject.Inject
+data class MayaPortalUIState(
+ val errorCode: Int? = null
+)
+
@OptIn(ExperimentalCoroutinesApi::class)
@HiltViewModel
class MayaViewModel @Inject constructor(
private val globalConfig: Configuration,
private val config: MayaConfig,
+ private val mayaApi: MayaApi,
+ private val fiatExchangeRateProvider: FiatExchangeRateProvider,
exchangeRatesProvider: ExchangeRatesProvider,
val analytics: AnalyticsService,
walletUIConfig: WalletUIConfig
) : ViewModel() {
+ companion object {
+ val log = LoggerFactory.getLogger(MayaViewModel::class.java)
+ }
- val balanceFormat: MonetaryFormat
- get() = globalConfig.format.noCode()
+ val fiatFormat = MonetaryFormat().minDecimals(2).withLocale(Locale.getDefault()).noCode()
val networkError = SingleLiveEvent()
private val _exchangeRate: MutableLiveData = MutableLiveData()
- val exchangeRate: LiveData
- get() = _exchangeRate
+
+ private var dashExchangeRate: org.bitcoinj.utils.ExchangeRate? = null
+ private var fiatExchangeRate: Fiat? = null
+
+ // val currencyList = MutableStateFlow>(listOf())
+
+ private val _uiState = MutableStateFlow(MayaPortalUIState())
+ val uiState: StateFlow = _uiState.asStateFlow()
val dashFormat: MonetaryFormat
get() = globalConfig.format.noCode()
+ val poolList = MutableStateFlow>(listOf())
+
init {
walletUIConfig.observe(WalletUIConfig.SELECTED_CURRENCY)
.filterNotNull()
.flatMapLatest(exchangeRatesProvider::observeExchangeRate)
- .onEach(_exchangeRate::postValue)
+ .onEach { rate ->
+ dashExchangeRate = rate?.let { org.bitcoinj.utils.ExchangeRate(Coin.COIN, rate.fiat) }
+ // val fiatBalance = exchangeRate?.coinToFiat(_uiState.value.balance)
+ _uiState.update { it.copy() }
+ }
+ .launchIn(viewModelScope)
+
+// mayaApi.observePoolList()
+// .filterNotNull()
+// .onEach {
+// log.info("Pool List: {}", it)
+// poolList.value = it
+// }
+// .launchIn(viewModelScope)
+
+ walletUIConfig.observe(WalletUIConfig.SELECTED_CURRENCY)
+ .filterNotNull()
+ .flatMapLatest(fiatExchangeRateProvider::observeFiatRate)
+ .onEach {
+ it?.let { fiatRate -> fiatExchangeRate = fiatRate.fiat }
+ log.info("exchange rate: $it")
+ }
+ .flatMapLatest { mayaApi.observePoolList(it!!.fiat) }
+ .onEach {
+ log.info("exchange rate in view model: {}", fiatExchangeRate?.toFriendlyString())
+ log.info("Pool List: {}", it)
+ log.info("Pool List: {}", it.map { pool -> pool.assetPriceFiat })
+ it.forEach { pool ->
+ pool.setAssetPrice(fiatExchangeRate!!)
+ }
+ poolList.value = it
+ }
.launchIn(viewModelScope)
}
+
+ fun formatFiat(fiatAmount: Fiat): String {
+ val localCurrencySymbol = GenericUtils.getLocalCurrencySymbol(fiatAmount.currencyCode)
+
+ val fiatBalance = fiatFormat.format(fiatAmount).toString()
+
+ return if (fiatAmount!!.isCurrencyFirst()) {
+ "$localCurrencySymbol $fiatBalance"
+ } else {
+ "$fiatBalance $localCurrencySymbol"
+ }
+ }
}
diff --git a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/utils/MayaConfig.kt b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/utils/MayaConfig.kt
index 6e2d0adf9b..560c545812 100644
--- a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/utils/MayaConfig.kt
+++ b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/utils/MayaConfig.kt
@@ -18,6 +18,7 @@
package org.dash.wallet.integrations.maya.utils
import android.content.Context
+import androidx.datastore.preferences.core.stringPreferencesKey
import org.dash.wallet.common.WalletDataProvider
import org.dash.wallet.common.data.BaseConfig
import javax.inject.Inject
@@ -30,5 +31,7 @@ open class MayaConfig @Inject constructor(
) : BaseConfig(context, PREFERENCES_NAME, walletDataProvider) {
companion object {
const val PREFERENCES_NAME = "maya"
+
+ val BACKGROUND_ERROR = stringPreferencesKey("error")
}
}
diff --git a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/utils/MayaConstants.kt b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/utils/MayaConstants.kt
index 3fe50d1fba..4dabf589b6 100644
--- a/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/utils/MayaConstants.kt
+++ b/integrations/maya/src/main/java/org/dash/wallet/integrations/maya/utils/MayaConstants.kt
@@ -17,7 +17,19 @@
package org.dash.wallet.integrations.maya.utils
+import org.bitcoinj.core.NetworkParameters
+
object MayaConstants {
+ const val DEFAULT_EXCHANGE_CURRENCY = "USD"
private const val MAINNET_BASE_URL = "https://midgard.mayachain.info/v2/"
+
+ /**
+ * https://exchangerate.host/#/docs
+ */
+ const val EXCHANGERATE_BASE_URL = "https://api.exchangerate.host/"
+
+ fun getBaseUrl(params: NetworkParameters): String {
+ return MAINNET_BASE_URL
+ }
}
diff --git a/integrations/maya/src/main/res/layout/fragment_currency_picker.xml b/integrations/maya/src/main/res/layout/fragment_currency_picker.xml
new file mode 100644
index 0000000000..a13cfb07cc
--- /dev/null
+++ b/integrations/maya/src/main/res/layout/fragment_currency_picker.xml
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/integrations/maya/src/main/res/navigation/nav_maya.xml b/integrations/maya/src/main/res/navigation/nav_maya.xml
new file mode 100644
index 0000000000..37fc60a06d
--- /dev/null
+++ b/integrations/maya/src/main/res/navigation/nav_maya.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/integrations/maya/src/main/res/values/strings-maya.xml b/integrations/maya/src/main/res/values/strings-maya.xml
index 57e2f16539..e6eed6fb9b 100644
--- a/integrations/maya/src/main/res/values/strings-maya.xml
+++ b/integrations/maya/src/main/res/values/strings-maya.xml
@@ -17,6 +17,19 @@
Maya
Convert Dash
+ Convert Dash to
From Dash Wallet to any crypto
+ BTC
+ Bitcoin
+ DASH
+ Dash
+ RUNE
+ Rune
+ ETH
+ USDC
+ USDT
+ Ethereum
+ USD Coin
+ USDT
\ No newline at end of file
diff --git a/wallet/res/navigation/nav_home.xml b/wallet/res/navigation/nav_home.xml
index 1334f27c98..6c82a6085b 100644
--- a/wallet/res/navigation/nav_home.xml
+++ b/wallet/res/navigation/nav_home.xml
@@ -215,7 +215,7 @@
-
-
+
\ No newline at end of file