Skip to content

Commit

Permalink
feat: warn user of stale exchange rates (#1284)
Browse files Browse the repository at this point in the history
* feat: support detection of stale rates

* feat: add new Toast based on Compose
  • Loading branch information
HashEngineering authored May 8, 2024
1 parent 99f0380 commit 4ef1b81
Show file tree
Hide file tree
Showing 22 changed files with 592 additions and 18 deletions.
1 change: 1 addition & 0 deletions common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ dependencies {
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
debugImplementation 'androidx.compose.ui:ui-tooling:1.0.0'

implementation "com.github.bumptech.glide:glide:$glideVersion"
ksp "com.github.bumptech.glide:compiler:$glideVersion"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.dash.wallet.common.data

import android.content.Context
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import org.dash.wallet.common.WalletDataProvider
import javax.inject.Inject

class ExchangeRatesConfig
@Inject
constructor(
context: Context,
walletDataProvider: WalletDataProvider
) : BaseConfig(
context,
PREFERENCES_NAME,
walletDataProvider
) {
companion object {
private const val PREFERENCES_NAME = "exchange_rates_config"
val EXCHANGE_RATES_RETRIEVAL_TIME = longPreferencesKey("exchange_rates_retrieval_time")
val EXCHANGE_RATES_RETRIEVAL_FAILURE = booleanPreferencesKey("exchange_rates_retrieval_error")
val EXCHANGE_RATES_PREVIOUS_RETRIEVAL_TIME = longPreferencesKey("exchange_rates_previous_retrieval_time")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,25 @@ open class ExchangeRate : Parcelable {
@PrimaryKey
var currencyCode: String
var rate: String? = null
@Ignore
var retrievalTime: Long = -1L

@Ignore
private var currencyName: String? = null

@Ignore
private var currency: Currency? = null

constructor(currencyCode: String, rate: String?, retrievalTime: Long) {
this.currencyCode = currencyCode
this.rate = rate
this.retrievalTime = retrievalTime
}

constructor(currencyCode: String, rate: String?) {
this.currencyCode = currencyCode
this.rate = rate
this.retrievalTime = -1
}

protected constructor(input: Parcel) {
Expand Down Expand Up @@ -79,13 +88,16 @@ open class ExchangeRate : Parcelable {
// If the currency is not a valid ISO 4217 code, then set the
// currency name to be equal to the currency code
// exchanges often have "invalid" currency codes like USDT and CNH
currencyName = if (currencyCode.length == 3) {
try {
getCurrency().displayName
} catch (x: IllegalArgumentException) {
currencyName =
if (currencyCode.length == 3) {
try {
getCurrency().displayName
} catch (x: IllegalArgumentException) {
currencyCode
}
} else {
currencyCode
}
} else currencyCode
if (currencyCode.equals(currencyName!!, ignoreCase = true)) {
currencyName = CurrencyInfo.getOtherCurrencyName(currencyCode, context)
}
Expand All @@ -97,7 +109,10 @@ open class ExchangeRate : Parcelable {
return 0
}

override fun writeToParcel(dest: Parcel, flags: Int) {
override fun writeToParcel(
dest: Parcel,
flags: Int
) {
dest.writeString(currencyCode)
dest.writeString(rate)
dest.writeString(currencyName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ interface ExchangeRatesProvider {
fun observeExchangeRate(currencyCode: String): Flow<ExchangeRate?>
suspend fun getExchangeRate(currencyCode: String): ExchangeRate?
suspend fun cleanupObsoleteCurrencies()
fun observeStaleRates(currencyCode: String): Flow<RateRetrievalState>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2024 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.services

data class RateRetrievalState(
val lastAttemptFailed: Boolean = false,
val staleRates: Boolean,
val volatile: Boolean
) {
val isStale = lastAttemptFailed || staleRates || volatile
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.dash.wallet.common.ui.components

import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.ComposeView

class ComposeHostFrameLayout
@JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private var composeView: ComposeView? = null

fun setContent(content: @Composable () -> Unit) {
if (composeView == null) {
composeView = ComposeView(context)
addView(composeView)
}
composeView?.setContent(content)
}

override fun removeAllViews() {
composeView = null
super.removeAllViews()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2024 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.components

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.sp
import org.dash.wallet.common.R

object MyTheme {
val ToastBackground = Color(0xff191c1f).copy(alpha = 0.9f)

val Body2Regular = TextStyle(
fontSize = 14.sp,
lineHeight = 20.sp,
fontFamily = FontFamily.Default, // FontFamily(Font(R.font.inter)) // crashes,
fontWeight = FontWeight(400),
color = Color.White
)

val OverlineSemibold = TextStyle(
fontSize = 12.sp,
lineHeight = 16.sp,
fontFamily = FontFamily.Default, // FontFamily(Font(R.font.inter)) // crashes,
fontWeight = FontWeight(600),
color = Color.White,
textAlign = TextAlign.Center
)
}
122 changes: 122 additions & 0 deletions common/src/main/java/org/dash/wallet/common/ui/components/Toast.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package org.dash.wallet.common.ui.components

import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.dash.wallet.common.R

enum class ToastDuration {
SHORT,
LONG,
INDEFINITE
}

enum class ToastImageResource(@DrawableRes val resourceId: Int) {
Information(R.drawable.ic_toast_info),
Warning(R.drawable.ic_toast_info_warning),
Copy(R.drawable.ic_toast_copy),
Error(R.drawable.ic_toast_error),
NoInternet(R.drawable.ic_toast_no_wifi)
}

@Composable
fun Toast(
text: String,
actionText: String,
modifier: Modifier = Modifier,
imageResource: Int? = null,
onActionClick: () -> Unit
) {
Box(
modifier =
modifier.fillMaxWidth()
.padding(horizontal = 5.dp, vertical = 5.dp)
.background(
color = MyTheme.ToastBackground,
shape = RoundedCornerShape(size = 10.dp)
)
) {
Row(
modifier =
Modifier
.fillMaxWidth()
.wrapContentSize(Alignment.BottomCenter)
.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Row(
modifier =
Modifier
.weight(1f),
verticalAlignment = Alignment.CenterVertically
) {
if (imageResource != null) {
Image(
painter = painterResource(id = imageResource),
contentDescription = null,
modifier =
Modifier
.size(15.dp)
)
}
Text(
text = text,
style = MyTheme.Body2Regular,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(start = 8.dp, end = 8.dp)
)
}
TextButton(
onClick = onActionClick,
modifier =
Modifier
.padding(start = 0.dp)
.wrapContentSize()
.padding(horizontal = 0.dp, vertical = 0.dp),
contentPadding = PaddingValues(0.dp)
) {
Text(
text = actionText,
style = MyTheme.OverlineSemibold,
textAlign = TextAlign.End
)
}
}
}
}

@Preview
@Composable
private fun ToastPreview() {
Box(Modifier.width(400.dp).height(100.dp).background(Color.White)) {
Toast(
text = "The exchange rates are out of date, please do something about it right away",
actionText = "OK",
Modifier,
R.drawable.ic_image_placeholder
) {
}
}
}
14 changes: 14 additions & 0 deletions common/src/main/res/drawable/ic_toast_copy.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M5.106,7.27C5.106,6.075 6.075,5.105 7.271,5.105H13.835C15.031,5.105 16,6.075 16,7.27V13.835C16,15.031 15.031,16 13.835,16H7.271C6.075,16 5.106,15.031 5.106,13.835V7.27Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
<path
android:pathData="M6.801,3.601C5.034,3.601 3.601,5.034 3.601,6.802C3.601,8.241 3.601,9.536 3.601,10.188C3.601,10.578 3.285,10.895 2.895,10.895H2.165C1.591,10.895 1.04,10.667 0.634,10.261C0.228,9.855 0,9.304 0,8.73V2.165C0,1.591 0.228,1.04 0.634,0.634C1.04,0.228 1.591,0 2.165,0H8.73C9.304,0 9.855,0.228 10.261,0.634C10.667,1.04 10.895,1.591 10.895,2.165V2.895C10.895,3.285 10.578,3.601 10.188,3.601C9.17,3.601 7.955,3.601 6.801,3.601Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
</vector>
10 changes: 10 additions & 0 deletions common/src/main/res/drawable/ic_toast_error.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="15dp"
android:height="15dp"
android:viewportWidth="15"
android:viewportHeight="15">
<path
android:pathData="m7.496,14.996c-1.026,0 -1.991,-0.196 -2.895,-0.588C3.696,14.021 2.898,13.482 2.206,12.79 1.519,12.098 0.98,11.3 0.588,10.396 0.196,9.491 0,8.526 0,7.5 0,6.474 0.196,5.509 0.588,4.605 0.98,3.7 1.519,2.904 2.206,2.217 2.898,1.525 3.694,0.983 4.594,0.591 5.498,0.2 6.463,0.004 7.489,0.004c1.03,0 1.998,0.196 2.903,0.588 0.905,0.392 1.703,0.934 2.395,1.626 0.692,0.687 1.234,1.483 1.626,2.388C14.804,5.509 15,6.474 15,7.5c0,1.026 -0.196,1.991 -0.588,2.896 -0.392,0.905 -0.934,1.703 -1.626,2.395 -0.692,0.692 -1.49,1.231 -2.395,1.618 -0.905,0.392 -1.87,0.588 -2.896,0.588zM5.116,10.57c0.198,0 0.368,-0.065 0.508,-0.196L7.504,8.48 9.39,10.374c0.131,0.131 0.295,0.196 0.493,0.196 0.194,0 0.356,-0.065 0.486,-0.196 0.135,-0.135 0.203,-0.298 0.203,-0.486 0,-0.198 -0.068,-0.36 -0.203,-0.486L8.476,7.507 10.377,5.613c0.136,-0.14 0.203,-0.302 0.203,-0.486 0,-0.189 -0.068,-0.348 -0.203,-0.479 -0.131,-0.135 -0.29,-0.203 -0.479,-0.203 -0.189,0 -0.351,0.068 -0.486,0.203L7.504,6.542 5.602,4.648C5.467,4.522 5.305,4.459 5.116,4.459c-0.189,0 -0.351,0.065 -0.486,0.196 -0.131,0.126 -0.196,0.288 -0.196,0.486 0,0.179 0.068,0.339 0.203,0.479L6.531,7.507 4.637,9.409C4.502,9.539 4.434,9.699 4.434,9.888c0,0.189 0.065,0.351 0.196,0.486 0.135,0.131 0.298,0.196 0.486,0.196z"
android:strokeWidth="0.990805"
android:fillColor="#ffffff"/>
</vector>
10 changes: 10 additions & 0 deletions common/src/main/res/drawable/ic_toast_info.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="15dp"
android:height="15dp"
android:viewportWidth="15"
android:viewportHeight="15">
<path
android:pathData="M7.496,15C6.471,15 5.506,14.804 4.601,14.412 3.696,14.025 2.898,13.485 2.206,12.793 1.519,12.101 0.98,11.302 0.588,10.397 0.196,9.492 0,8.526 0,7.5 0,6.474 0.196,5.508 0.588,4.603 0.98,3.698 1.519,2.902 2.206,2.214 2.898,1.522 3.694,0.98 4.594,0.588 5.498,0.196 6.463,0 7.489,0c1.03,0 1.998,0.196 2.903,0.588 0.905,0.392 1.703,0.934 2.395,1.626 0.692,0.687 1.234,1.484 1.626,2.389C14.804,5.508 15,6.474 15,7.5c0,1.026 -0.196,1.992 -0.588,2.897 -0.392,0.905 -0.934,1.704 -1.626,2.396 -0.692,0.692 -1.49,1.232 -2.395,1.619C9.487,14.804 8.522,15 7.496,15ZM6.168,11.675h3.055c0.174,0 0.319,-0.053 0.435,-0.16 0.116,-0.111 0.174,-0.254 0.174,-0.428 0,-0.164 -0.058,-0.303 -0.174,-0.414C9.543,10.557 9.398,10.498 9.224,10.498H8.389V6.941C8.389,6.713 8.333,6.53 8.222,6.389 8.111,6.249 7.949,6.179 7.736,6.179H6.299c-0.169,0 -0.312,0.058 -0.428,0.174 -0.116,0.116 -0.174,0.254 -0.174,0.414 0,0.174 0.058,0.317 0.174,0.428 0.116,0.106 0.259,0.16 0.428,0.16h0.762v3.144h-0.893c-0.169,0 -0.314,0.058 -0.435,0.174 -0.116,0.111 -0.174,0.249 -0.174,0.414 0,0.174 0.058,0.317 0.174,0.428 0.121,0.106 0.266,0.16 0.435,0.16zM7.453,5.01c0.29,0 0.535,-0.102 0.733,-0.305C8.384,4.501 8.483,4.259 8.483,3.979 8.483,3.683 8.384,3.437 8.186,3.238 7.987,3.035 7.743,2.933 7.453,2.933 7.167,2.933 6.923,3.035 6.72,3.238 6.517,3.437 6.415,3.683 6.415,3.979c0,0.281 0.102,0.523 0.305,0.726 0.203,0.203 0.448,0.305 0.733,0.305z"
android:fillColor="#ffffff"
android:fillAlpha="0.9"/>
</vector>
10 changes: 10 additions & 0 deletions common/src/main/res/drawable/ic_toast_info_warning.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="15dp"
android:height="15dp"
android:viewportWidth="15"
android:viewportHeight="15">
<path
android:pathData="m2.015,14.308c-0.412,0 -0.769,-0.09 -1.071,-0.27C0.641,13.859 0.407,13.618 0.241,13.315 0.08,13.012 0,12.676 0,12.307c0,-0.36 0.095,-0.702 0.284,-1.029L5.776,1.706C5.96,1.371 6.209,1.118 6.521,0.947 6.833,0.777 7.159,0.692 7.5,0.692c0.341,0 0.665,0.085 0.972,0.255 0.307,0.166 0.558,0.419 0.752,0.759L14.716,11.278c0.095,0.161 0.166,0.329 0.213,0.504 0.047,0.175 0.071,0.35 0.071,0.525 0,0.369 -0.083,0.705 -0.248,1.008 -0.166,0.303 -0.4,0.544 -0.702,0.724 -0.298,0.18 -0.653,0.27 -1.064,0.27zM7.507,9.448c0.421,0 0.636,-0.22 0.646,-0.66L8.266,5.226C8.276,5.008 8.207,4.831 8.061,4.694 7.914,4.552 7.727,4.481 7.5,4.481c-0.232,0 -0.419,0.071 -0.561,0.213C6.798,4.831 6.731,5.006 6.741,5.219l0.099,3.576c0.014,0.435 0.237,0.653 0.667,0.653zM7.507,11.889c0.237,0 0.44,-0.076 0.61,-0.227 0.17,-0.151 0.255,-0.345 0.255,-0.582 0,-0.232 -0.085,-0.426 -0.255,-0.582 -0.17,-0.156 -0.374,-0.234 -0.61,-0.234 -0.241,0 -0.447,0.078 -0.617,0.234 -0.17,0.156 -0.255,0.35 -0.255,0.582 0,0.237 0.085,0.43 0.255,0.582 0.175,0.151 0.381,0.227 0.617,0.227z"
android:strokeWidth="0.967745"
android:fillColor="#ffffff"/>
</vector>
9 changes: 9 additions & 0 deletions common/src/main/res/drawable/ic_toast_no_wifi.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="17dp"
android:height="14dp"
android:viewportWidth="17"
android:viewportHeight="14">
<path
android:pathData="M3.07,3.056L4.804,4.782C3.663,5.259 2.639,5.974 1.734,6.928C1.652,7.015 1.553,7.06 1.438,7.065C1.327,7.07 1.231,7.029 1.149,6.942L0.123,5.909C0.046,5.832 0.005,5.748 0,5.656C0,5.56 0.031,5.476 0.094,5.404C0.474,4.941 0.918,4.512 1.423,4.118C1.929,3.718 2.478,3.364 3.07,3.056ZM8.381,4.06L6.235,1.914C6.635,1.837 7.02,1.779 7.391,1.741C7.766,1.702 8.137,1.683 8.503,1.683C9.626,1.683 10.717,1.844 11.776,2.167C12.836,2.485 13.806,2.923 14.688,3.482C15.574,4.04 16.316,4.681 16.913,5.404C16.975,5.476 17.004,5.56 17,5.656C17,5.748 16.961,5.832 16.884,5.909L15.858,6.935C15.781,7.017 15.685,7.06 15.569,7.065C15.458,7.07 15.362,7.031 15.28,6.95C14.399,6.034 13.399,5.329 12.282,4.833C11.164,4.337 9.941,4.074 8.612,4.045C8.578,4.045 8.539,4.048 8.496,4.053C8.458,4.053 8.419,4.055 8.381,4.06ZM6.336,6.299L8.337,8.308C7.884,8.317 7.441,8.392 7.008,8.532C6.574,8.671 6.167,8.859 5.787,9.095C5.411,9.331 5.076,9.603 4.783,9.912C4.696,10.008 4.597,10.061 4.486,10.071C4.376,10.075 4.275,10.035 4.183,9.948L3.034,8.828C2.957,8.756 2.916,8.676 2.912,8.59C2.907,8.498 2.936,8.414 2.998,8.337C3.388,7.884 3.868,7.482 4.436,7.13C5.009,6.774 5.642,6.497 6.336,6.299ZM13.561,9.24L10.613,6.285C11.297,6.463 11.937,6.735 12.535,7.101C13.137,7.467 13.628,7.879 14.009,8.337C14.071,8.414 14.1,8.498 14.095,8.59C14.09,8.676 14.049,8.756 13.972,8.828L13.561,9.24ZM8.503,10.215C9.014,10.215 9.488,10.328 9.927,10.555C10.365,10.781 10.714,11.06 10.974,11.393C11.037,11.475 11.063,11.564 11.054,11.66C11.044,11.756 11.003,11.838 10.931,11.906L9.154,13.618C9.004,13.762 8.881,13.861 8.785,13.914C8.694,13.967 8.6,13.994 8.503,13.994C8.417,13.994 8.325,13.967 8.229,13.914C8.132,13.861 8.01,13.762 7.86,13.618L6.083,11.906C6.021,11.838 5.98,11.766 5.96,11.689C5.941,11.607 5.955,11.53 6.004,11.458C6.264,11.106 6.615,10.812 7.058,10.576C7.506,10.335 7.988,10.215 8.503,10.215ZM14.608,13.242L2.37,1.011C2.259,0.9 2.204,0.763 2.204,0.599C2.204,0.431 2.259,0.289 2.37,0.173C2.48,0.057 2.618,0.002 2.781,0.007C2.95,0.007 3.092,0.065 3.208,0.18L15.446,12.419C15.562,12.529 15.617,12.667 15.612,12.83C15.612,12.989 15.557,13.127 15.446,13.242C15.335,13.353 15.198,13.408 15.034,13.408C14.871,13.413 14.729,13.358 14.608,13.242Z"
android:fillColor="#ffffff"/>
</vector>
Loading

0 comments on commit 4ef1b81

Please sign in to comment.