Skip to content

Commit

Permalink
feat(maya): add currency picker with local currency prices
Browse files Browse the repository at this point in the history
  • Loading branch information
HashEngineering committed Sep 20, 2023
1 parent 0d0d9b2 commit 6aaa93b
Show file tree
Hide file tree
Showing 23 changed files with 1,029 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class RadioGroupAdapter(
}
}

class RadioButtonViewHolder(
open class RadioButtonViewHolder(
val binding: RadiobuttonRowBinding,
private val isCheckMark: Boolean
) : RecyclerView.ViewHolder(binding.root) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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<IconifiedViewItem, IconifiedViewHolder>(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)
}
}
103 changes: 103 additions & 0 deletions common/src/main/res/layout/iconifieditem_row.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 <http://www.gnu.org/licenses/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="46dp"
android:paddingHorizontal="15dp"
android:paddingVertical="12dp"
tools:context=".ui.radio_group.RadioGroupAdapter">

<FrameLayout
android:id="@+id/icon_wrapper"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@drawable/rounded_background"
android:theme="@style/EncircledIconTheme"
android:layout_marginEnd="12dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/title">

<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
tools:src="@drawable/ic_chevron" />
</FrameLayout>

<TextView
android:id="@+id/title"
style="@style/Body2.Medium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="@color/radiobutton_text_color"
android:layout_marginEnd="12dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/subtitle"
app:layout_constraintStart_toEndOf="@id/icon_wrapper"
app:layout_constraintEnd_toStartOf="@id/additional_info"
tools:text="Alabama" />

<TextView
android:id="@+id/subtitle"
style="@style/Caption.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:drawablePadding="4dp"
android:layout_marginEnd="12dp"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/additional_info"
tools:text="****1234" />

<TextView
android:id="@+id/additional_info"
style="@style/Body2.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/additional_info_subtitle"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible"
tools:text="USD" />

<TextView
android:id="@+id/additional_info_subtitle"
style="@style/Caption.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:drawablePadding="4dp"
android:layout_marginEnd="12dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
tools:visibility="visible"
app:layout_constraintTop_toBottomOf="@id/additional_info"
app:layout_constraintEnd_toEndOf="parent"
tools:text="****1234" />

</androidx.constraintlayout.widget.ConstraintLayout>
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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<ExchangeRateResponse>
}
Original file line number Diff line number Diff line change
@@ -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<ExchangeRate>
fun observeFiatRates(): Flow<List<ExchangeRate>>
fun observeFiatRate(currencyCode: String): Flow<ExchangeRate?>
}

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<List<ExchangeRate>> {
TODO("Not yet implemented")
}

override fun observeFiatRate(currencyCode: String): Flow<ExchangeRate?> {
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
}
}
}
Loading

0 comments on commit 6aaa93b

Please sign in to comment.