diff --git a/build.gradle b/build.gradle index 5e668fe31e..7b83dddaf4 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ buildscript { // UI materialVersion = "1.6.1" browserVersion = "1.3.0" - constrainLayoutVersion = '2.1.4' + constrainLayoutVersion = '2.2.1' recyclerViewVersion = "1.2.1" glideVersion = '4.12.0' zxingVersion = '3.3.3' // Don't update. 3.3.3 is the maximum to support Android 6.x diff --git a/common/src/main/java/org/dash/wallet/common/data/WalletUIConfig.kt b/common/src/main/java/org/dash/wallet/common/data/WalletUIConfig.kt index 5c6ba0dabe..48394f4f56 100644 --- a/common/src/main/java/org/dash/wallet/common/data/WalletUIConfig.kt +++ b/common/src/main/java/org/dash/wallet/common/data/WalletUIConfig.kt @@ -27,6 +27,7 @@ import org.dash.wallet.common.services.ExchangeRatesProvider import org.dash.wallet.common.util.Constants import javax.inject.Inject import javax.inject.Singleton +import androidx.core.content.edit @Singleton // Intended for the UI settings which affect what the user sees on the screen. @@ -65,6 +66,8 @@ open class WalletUIConfig @Inject constructor( val EXCHANGE_CURRENCY_DETECTED = booleanPreferencesKey("exchange_currency_detected") val LAST_TOTAL_BALANCE = longPreferencesKey("last_total_balance") val LAST_MIXED_BALANCE = longPreferencesKey("last_mixed_balance") + val CUSTOMIZED_SHORTCUTS = stringPreferencesKey("customized_shortcuts") + val IS_SHORTCUT_INFO_HIDDEN = booleanPreferencesKey("is_shortcut_info_hidden") } suspend fun getExchangeCurrencyCode(): String { @@ -111,7 +114,7 @@ class ExchangeCurrencyMigration( } else { otherName } - sharedPreferences.edit().putString(WalletUIConfig.SELECTED_CURRENCY.name, fixedValue).apply() + sharedPreferences.edit { putString(WalletUIConfig.SELECTED_CURRENCY.name, fixedValue) } } // The database might have obsolete currencies as well exchangeRates.cleanupObsoleteCurrencies() diff --git a/common/src/main/java/org/dash/wallet/common/services/analytics/AnalyticsConstants.kt b/common/src/main/java/org/dash/wallet/common/services/analytics/AnalyticsConstants.kt index e0fa92ef66..8082e52eab 100644 --- a/common/src/main/java/org/dash/wallet/common/services/analytics/AnalyticsConstants.kt +++ b/common/src/main/java/org/dash/wallet/common/services/analytics/AnalyticsConstants.kt @@ -126,6 +126,7 @@ object AnalyticsConstants { const val SHORTCUT_SCAN_TO_PAY = "shortcut_scan_to_pay" const val SHORTCUT_SEND_TO_ADDRESS = "shortcut_send_to_address" const val SHORTCUT_RECEIVE = "shortcut_receive" + const val SHORTCUT_SEND = "shortcut_send" const val SHORTCUT_BUY_AND_SELL = "shortcut_buy_and_sell_dash" const val SHORTCUT_EXPLORE = "shortcut_explore" const val HIDE_BALANCE = "home_hide_balance" diff --git a/common/src/main/java/org/dash/wallet/common/ui/components/InfoPanel.kt b/common/src/main/java/org/dash/wallet/common/ui/components/InfoPanel.kt new file mode 100644 index 0000000000..303a38a6cc --- /dev/null +++ b/common/src/main/java/org/dash/wallet/common/ui/components/InfoPanel.kt @@ -0,0 +1,123 @@ +/* + * 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 . + */ + +package org.dash.wallet.common.ui.components + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.dash.wallet.common.R + +@Composable +fun InfoPanel( + title: String, + description: String, + modifier: Modifier = Modifier, + @DrawableRes leftIconRes: Int? = null, + @DrawableRes actionIconRes: Int? = null, + onAction: (() -> Unit)? = null +) { + Box( + modifier = modifier + .fillMaxWidth() + .background(MyTheme.Colors.backgroundSecondary, RoundedCornerShape(16.dp)) + .shadow(elevation = 20.dp, spotColor = Color(0x1AB8C1CC), ambientColor = Color(0x1AB8C1CC)), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(14.dp, 15.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + leftIconRes?.let { + Box( + modifier = Modifier + .size(34.dp), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = leftIconRes), + contentDescription = null, + tint = Color.Unspecified + ) + } + } + + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(2.dp) + ) { + Text( + text = title, + style = MyTheme.CaptionMedium + ) + + Text( + text = description, + style = MyTheme.Caption, + color = MyTheme.Colors.textSecondary + ) + } + + if (actionIconRes != null && onAction != null) { + Box( + modifier = Modifier + .size(28.dp) + .clickable { onAction() }, + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = actionIconRes), + contentDescription = "Close", + tint = MyTheme.Colors.gray + ) + } + } + } + } +} + +@Preview +@Composable +fun InfoPanelPreview() { + InfoPanel( + title = "Customize shortcut bar", + description = "Hold any button above to replace it with the function you need", + leftIconRes = R.drawable.ic_dash_blue_filled, + actionIconRes = R.drawable.ic_popup_close, + onAction = {} + ) +} \ No newline at end of file diff --git a/common/src/main/java/org/dash/wallet/common/ui/components/MenuItem.kt b/common/src/main/java/org/dash/wallet/common/ui/components/MenuItem.kt index 3df31e41c2..43407fec0d 100644 --- a/common/src/main/java/org/dash/wallet/common/ui/components/MenuItem.kt +++ b/common/src/main/java/org/dash/wallet/common/ui/components/MenuItem.kt @@ -56,7 +56,7 @@ fun MenuItem( modifier = Modifier .fillMaxWidth() .padding(10.dp) - .background(Color.White, RoundedCornerShape(8.dp)) + .background(MyTheme.Colors.backgroundSecondary, RoundedCornerShape(8.dp)) .clickable { action?.invoke() } .padding(10.dp), verticalAlignment = Alignment.CenterVertically diff --git a/common/src/main/java/org/dash/wallet/common/ui/components/MyTheme.kt b/common/src/main/java/org/dash/wallet/common/ui/components/MyTheme.kt index 629d293f4b..a24f0cdfd8 100644 --- a/common/src/main/java/org/dash/wallet/common/ui/components/MyTheme.kt +++ b/common/src/main/java/org/dash/wallet/common/ui/components/MyTheme.kt @@ -28,12 +28,15 @@ import org.dash.wallet.common.R object MyTheme { val ToastBackground = Color(0xff191c1f).copy(alpha = 0.9f) - val InterFont = FontFamily(Font(R.font.inter)) + private val interRegular = FontFamily(Font(R.font.inter_regular)) + private val interMedium = FontFamily(Font(R.font.inter_medium)) + private val interSemibold = FontFamily(Font(R.font.inter_semibold)) + private val interBold = FontFamily(Font(R.font.inter_bold)) val Micro = TextStyle( fontSize = 10.sp, lineHeight = 16.sp, - fontFamily = InterFont, + fontFamily = interRegular, fontWeight = FontWeight(500), textAlign = TextAlign.Center, ) @@ -41,22 +44,37 @@ object MyTheme { val CaptionMedium = TextStyle( fontSize = 13.sp, lineHeight = 18.sp, - fontFamily = InterFont, + fontFamily = interMedium, fontWeight = FontWeight(500) ) + val Overline = TextStyle( + fontSize = 12.sp, + lineHeight = 16.sp, + fontFamily = interMedium, + fontWeight = FontWeight(500), + textAlign = TextAlign.Center + ) + val OverlineSemibold = TextStyle( fontSize = 12.sp, lineHeight = 16.sp, - fontFamily = InterFont, + fontFamily = interSemibold, fontWeight = FontWeight(600), textAlign = TextAlign.Center ) + val Caption = TextStyle( + fontSize = 13.sp, + lineHeight = 18.sp, + fontFamily = interRegular, + fontWeight = FontWeight(400) + ) + val OverlineMedium = TextStyle( fontSize = 12.sp, lineHeight = 16.sp, - fontFamily = InterFont, + fontFamily = interMedium, fontWeight = FontWeight(500), textAlign = TextAlign.Center ) @@ -64,21 +82,21 @@ object MyTheme { val Body2Regular = TextStyle( fontSize = 14.sp, lineHeight = 20.sp, - fontFamily = InterFont, + fontFamily = interRegular, fontWeight = FontWeight(400) ) val SubtitleSemibold = TextStyle( fontSize = 16.sp, lineHeight = 22.sp, - fontFamily = InterFont, + fontFamily = interSemibold, fontWeight = FontWeight(600) ) val H6Bold = TextStyle( fontSize = 20.sp, lineHeight = 26.sp, - fontFamily = InterFont, + fontFamily = interBold, fontWeight = FontWeight(700) ) @@ -93,6 +111,7 @@ object MyTheme { val primary40 = Color(0x66191C1F) val dashBlue = Color(0xFF008DE4) val dashBlue5 = Color(0x0D008DE4) + val gray = Color(0xFFB0B6BC) val gray400 = Color(0xFF75808A) } } diff --git a/common/src/main/java/org/dash/wallet/common/ui/receive/ReceiveInfoView.kt b/common/src/main/java/org/dash/wallet/common/ui/receive/ReceiveInfoView.kt index 051b326a59..bddbc311b5 100644 --- a/common/src/main/java/org/dash/wallet/common/ui/receive/ReceiveInfoView.kt +++ b/common/src/main/java/org/dash/wallet/common/ui/receive/ReceiveInfoView.kt @@ -80,7 +80,7 @@ class ReceiveInfoView(context: Context, attrs: AttributeSet?) : ConstraintLayout } binding.shareButton.setOnClickListener { onShareClicked?.invoke() - handleShare(paymentRequestUri) + address?.let { handleShare(it.toBase58()) } } refresh() diff --git a/common/src/main/java/org/dash/wallet/common/util/GenericUtils.java b/common/src/main/java/org/dash/wallet/common/util/GenericUtils.java deleted file mode 100644 index f7ea83fcb2..0000000000 --- a/common/src/main/java/org/dash/wallet/common/util/GenericUtils.java +++ /dev/null @@ -1,274 +0,0 @@ -///* -// * Copyright 2011-2015 the original author or authors. -// * -// * 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.util; -// -//import android.content.Context; -//import android.graphics.drawable.Drawable; -//import android.text.Spannable; -//import android.text.SpannableStringBuilder; -//import android.text.style.ImageSpan; -// -//import androidx.core.content.res.ResourcesCompat; -// -//import org.dash.wallet.common.R; -// -//import android.net.ConnectivityManager; -//import android.os.Build; -//import android.os.LocaleList; -//import android.text.TextUtils; -//import android.widget.Toast; -// -//import org.bitcoinj.utils.Fiat; -//import org.bitcoinj.utils.MonetaryFormat; -//import org.dash.wallet.common.Constants; -//import org.dash.wallet.common.data.CurrencyInfo; -// -//import java.text.NumberFormat; -//import java.util.Currency; -//import java.util.Locale; -//import java.util.Objects; -// -///** -// * @author Andreas Schildbach -// */ -//public class GenericUtils { TODO -// -// public static boolean startsWithIgnoreCase(final String string, final String prefix) { -// return string.regionMatches(true, 0, prefix, 0, prefix.length()); -// } -// -// public static String currencySymbol(final String currencyCode) { -// try { -// final Currency currency = Currency.getInstance(currencyCode); -// return currency.getSymbol(); -// } catch (final IllegalArgumentException x) { -// return currencyCode; -// } -// } -// -// public static Spannable appendDashSymbol(Context context, CharSequence text, boolean spaceBefore, boolean spaceAfter, float scale) { -// return insertDashSymbol(context, text, text.length(), spaceBefore, spaceAfter, scale); -// } -// -// public static Spannable insertDashSymbol(Context context, CharSequence text, int position, boolean spaceBefore, boolean spaceAfter, float scale) { -// -// Drawable drawableDash = ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_dash_d_black, null); -// if (drawableDash == null) { -// return null; -// } -// int size = (int) (scale * 32); -// drawableDash.setBounds(0, 0, size, size); -// ImageSpan dashSymbol = new ImageSpan(drawableDash, ImageSpan.ALIGN_BASELINE); -// -// SpannableStringBuilder builder = new SpannableStringBuilder(text); -// if (spaceBefore) { -// builder.insert(position++, " "); -// } -// builder.insert(position, " "); -// if (spaceAfter) { -// builder.insert(position + 1, " "); -// } -// builder.setSpan(dashSymbol, position, position + 1, 0); -// -// return builder; -// } -// -// public static boolean isInternetConnected(Context context) { -// ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); -// -// return cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected(); -// } -// -// public static void showToast(Context context, String messages) { -// Toast.makeText(context, messages, Toast.LENGTH_LONG).show(); -// } -// -// /** -// * Function which returns a concatenation of the currency code or currency symbol -// * For currencies used by multiple countries, we set a locale with any country using the currency -// * If the currentCurrencySymbol equals the currency code, we just use the currency code, otherwise we -// * get the symbol -// * @param currencyCode -// * @return -// */ -// public static String setCurrentCurrencySymbolWithCode(String currencyCode) { -// Locale currentLocale = new Locale("", ""); -// String currentCurrencySymbol = ""; -// switch (currencyCode.toLowerCase(Locale.ROOT)) { -// case "eur": -// currentLocale = Locale.FRANCE; -// break; -// case "xof": -// currentLocale = new Locale("fr", "CM"); -// break; -// case "xaf": -// currentLocale = new Locale("fr", "SN"); -// break; -// case "cfp": -// currentLocale = new Locale("fr", "NC"); -// break; -// case "hkd": -// currentLocale = new Locale("en", "HK"); -// break; -// case "bnd": -// currentLocale = new Locale("ms", "BN"); -// break; -// case "aud": -// currentLocale = new Locale("en", "AU"); -// break; -// case "gbp": -// currentLocale = Locale.UK; -// break; -// case "inr": -// currentLocale = new Locale("en", "IN"); -// break; -// case "nzd": -// currentLocale = new Locale("en", "NZ"); -// break; -// case "ils": -// currentLocale = new Locale("iw", "IL"); -// break; -// case "jod": -// currentLocale = new Locale("ar", "JO"); -// break; -// case "rub": -// currentLocale = new Locale("ru", "RU"); -// break; -// case "zar": -// currentLocale = new Locale("en", "ZA"); -// break; -// case "chf": -// currentLocale = new Locale("fr", "CH"); -// break; -// case "try": -// currentLocale = new Locale("tr", "TR"); -// break; -// case "usd": -// currentLocale = Locale.US; -// break; -// } -// currentCurrencySymbol = TextUtils.isEmpty(currentLocale.getLanguage()) ? -// currencySymbol(currencyCode.toLowerCase(Locale.ROOT)) : Currency.getInstance(currentLocale).getSymbol(); -// -// return String.format(getDeviceLocale(), "%s", -// currencyCode.equalsIgnoreCase(currentCurrencySymbol) ? currencyCode : currentCurrencySymbol); -// } -// -// public static Locale getDeviceLocale() { -// String countryCode = ""; -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { -// countryCode = LocaleList.getDefault().get(0).getCountry(); -// } else { -// countryCode = Locale.getDefault().getCountry(); -// } -// String deviceLocaleLanguage = Locale.getDefault().getLanguage(); -// return new Locale(deviceLocaleLanguage, countryCode); -// } -// -// public static FiatAmountFormat formatFiatFromLocale(CharSequence fiatValue) { -// String valWithoutLetters = stripLettersFromString(fiatValue.toString()); -// String valWithoutComma = formatFiatWithoutComma(valWithoutLetters); -// Double fiatAsDouble; -// // we may get a NumberFormatException -// try { -// fiatAsDouble = valWithoutComma.length() == 0 ? 0.00 : Double.parseDouble(valWithoutComma); -// } catch (NumberFormatException x) { -// fiatAsDouble = 0.00; -// } -// NumberFormat numberFormat = NumberFormat.getCurrencyInstance(getDeviceLocale()); -// String formattedStringValue = numberFormat.format(fiatAsDouble); -// // get currency symbol and code to remove explicitly -// String currencyCode = numberFormat.getCurrency().getCurrencyCode(); -// String currencySymbol = numberFormat.getCurrency().getSymbol(); -// return new FiatAmountFormat(Character.isDigit(formattedStringValue.charAt(0)), stripCurrencyFromString(formattedStringValue, currencySymbol, currencyCode)); -// } -// -// /** -// * Keep numericals, minus, dot, comma -// */ -// private static String stripLettersFromString(String st) { -// return st.replaceAll("[^\\d,.-]", ""); -// } -// -// /** -// * Remove currency symbols and codes from the string -// */ -// private static String stripCurrencyFromString(String st, String symbol, String code) { -// return stripLettersFromString(st.replaceAll(symbol, "").replaceAll(code, "")); -// } -// -// /** -// * To perform some operations on our fiat values (ex: parse to double, convert fiat to Coin), it needs to be properly formatted -// * In case our fiat value is in a currency that has a comma, we need to strip it away so as to have our value as a decimal -// * @param fiatValue -// * @return -// */ -// public static String formatFiatWithoutComma(String fiatValue){ -// boolean fiatValueContainsCommaWithDecimal = fiatValue.contains(",") && fiatValue.contains("."); -// return fiatValueContainsCommaWithDecimal ? fiatValue.replaceAll(",", "") : fiatValue.replaceAll(",", "."); -// } -// -// public static String fiatToString(Fiat fiat) { -// MonetaryFormat format = Constants.SEND_PAYMENT_LOCAL_FORMAT.noCode(); -// NumberFormat numberFormat = NumberFormat.getCurrencyInstance(getDeviceLocale()); -// Currency currency = Currency.getInstance(fiat.currencyCode); -// numberFormat.setCurrency(currency); -// String currencySymbol = currency.getSymbol(getDeviceLocale()); -// boolean isCurrencyFirst = numberFormat.format(1.0).startsWith(currencySymbol); -// -// if (isCurrencyFirst) { -// return currencySymbol + " " + format.format(fiat); -// } else { -// return format.format(fiat) + " " + currencySymbol; -// } -// } -// -// public static boolean isCurrencyFirst(Fiat fiat) { -// NumberFormat numberFormat = NumberFormat.getCurrencyInstance(getDeviceLocale()); -// Currency currency = Currency.getInstance(fiat.currencyCode); -// numberFormat.setCurrency(currency); -// String currencySymbol = currency.getSymbol(getDeviceLocale()); -// return numberFormat.format(1.0).startsWith(currencySymbol); -// } -// -// public static String getLocalCurrencySymbol(String currencyCode) { -// NumberFormat numberFormat = NumberFormat.getCurrencyInstance(getDeviceLocale()); -// Currency currency = Currency.getInstance(currencyCode); -// numberFormat.setCurrency(currency); -// return currency.getSymbol(getDeviceLocale()); -// } -// -// public static String fiatToStringWithoutCurrencyCode(Fiat fiat) { -// MonetaryFormat format = Constants.SEND_PAYMENT_LOCAL_FORMAT.noCode(); -// return format.format(fiat).toString(); -// } -// -// public static String getCoinIcon(String code) { -// return "https://raw.githubusercontent.com/jsupa/crypto-icons/main/icons/"+code.toLowerCase()+".png"; -// } -// -// public static String getLocaleCurrencyCode(){ -// Currency currency = Currency.getInstance(getDeviceLocale()); -// String newCurrencyCode = currency.getCurrencyCode(); -// if (CurrencyInfo.hasObsoleteCurrency(newCurrencyCode)) { -// newCurrencyCode = CurrencyInfo.getUpdatedCurrency(newCurrencyCode); -// } -// newCurrencyCode = CurrencyInfo.getOtherName(newCurrencyCode); -// return newCurrencyCode; -// } -//} diff --git a/wallet/build.gradle b/wallet/build.gradle index 6c32b9261c..30ed4bf565 100644 --- a/wallet/build.gradle +++ b/wallet/build.gradle @@ -102,6 +102,7 @@ dependencies { implementation("androidx.compose.ui:ui-tooling") implementation("androidx.compose.material3:material3") implementation("androidx.activity:activity-compose:1.7.2") + implementation "androidx.lifecycle:lifecycle-runtime-compose:$lifecycleVersion" // Navigation implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion" diff --git a/wallet/res/drawable/ic_arrow_drop_down_blue.xml b/wallet/res/drawable/ic_arrow_drop_down_blue.xml deleted file mode 100644 index 48e5bd621c..0000000000 --- a/wallet/res/drawable/ic_arrow_drop_down_blue.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/wallet/res/drawable/ic_arrow_drop_up_blue.xml b/wallet/res/drawable/ic_arrow_drop_up_blue.xml deleted file mode 100644 index 40906b4624..0000000000 --- a/wallet/res/drawable/ic_arrow_drop_up_blue.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/wallet/res/drawable/ic_arrow_icon.xml b/wallet/res/drawable/ic_arrow_icon.xml deleted file mode 100644 index 9146fad55c..0000000000 --- a/wallet/res/drawable/ic_arrow_icon.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/wallet/res/drawable/ic_avatar_blue.xml b/wallet/res/drawable/ic_avatar_blue.xml deleted file mode 100644 index a82e4d19f5..0000000000 --- a/wallet/res/drawable/ic_avatar_blue.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - diff --git a/wallet/res/drawable/ic_balance_warning.xml b/wallet/res/drawable/ic_balance_warning.xml deleted file mode 100644 index 12817b42ce..0000000000 --- a/wallet/res/drawable/ic_balance_warning.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/wallet/res/drawable/ic_blue_clock.xml b/wallet/res/drawable/ic_blue_clock.xml deleted file mode 100644 index e298d60b0f..0000000000 --- a/wallet/res/drawable/ic_blue_clock.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/wallet/res/drawable/ic_contact.xml b/wallet/res/drawable/ic_contact.xml new file mode 100644 index 0000000000..cde4523645 --- /dev/null +++ b/wallet/res/drawable/ic_contact.xml @@ -0,0 +1,14 @@ + + + + diff --git a/wallet/res/drawable/ic_explore.xml b/wallet/res/drawable/ic_explore.xml index 1d5418d19a..8966f39995 100644 --- a/wallet/res/drawable/ic_explore.xml +++ b/wallet/res/drawable/ic_explore.xml @@ -1,45 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> - - - - - - - - - - - - - - - - - + android:pathData="M12,0C18.626,0 24,5.374 24,12C24,18.626 18.626,24 12,24C5.374,24 0,18.636 0,12C0,5.363 5.363,0 12,0ZM16.623,8.056C16.758,7.638 16.372,7.242 15.955,7.367L9.809,9.287C9.558,9.37 9.35,9.558 9.277,9.819L7.357,15.976C7.221,16.383 7.617,16.779 8.024,16.643L14.139,14.724C14.39,14.65 14.598,14.452 14.671,14.191L16.612,8.045L16.623,8.056Z" + android:fillColor="#78C4F5"/> diff --git a/wallet/res/drawable/ic_receive_payments.xml b/wallet/res/drawable/ic_receive_payments.xml deleted file mode 100644 index d1a14d955e..0000000000 --- a/wallet/res/drawable/ic_receive_payments.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/wallet/res/drawable/ic_send_to_address.xml b/wallet/res/drawable/ic_send_to_address.xml new file mode 100644 index 0000000000..28d63f021a --- /dev/null +++ b/wallet/res/drawable/ic_send_to_address.xml @@ -0,0 +1,9 @@ + + + diff --git a/wallet/res/drawable/ic_shortcut_atm.xml b/wallet/res/drawable/ic_shortcut_atm.xml new file mode 100644 index 0000000000..4e27150ab9 --- /dev/null +++ b/wallet/res/drawable/ic_shortcut_atm.xml @@ -0,0 +1,12 @@ + + + + diff --git a/wallet/res/drawable/ic_shortcut_buy_sell_dash.xml b/wallet/res/drawable/ic_shortcut_buy_sell_dash.xml index e242323954..c215252dfe 100644 --- a/wallet/res/drawable/ic_shortcut_buy_sell_dash.xml +++ b/wallet/res/drawable/ic_shortcut_buy_sell_dash.xml @@ -1,39 +1,12 @@ + android:width="24dp" + android:height="20dp" + android:viewportWidth="24" + android:viewportHeight="20"> - - - - - - - + android:pathData="M19,0H5C2.24,0 0,2.24 0,5V6H24V5C24,2.24 21.76,0 19,0Z" + android:fillColor="#008DE4"/> - - - - - - - - + android:pathData="M0,15C0,17.76 2.24,20 5,20H19C21.76,20 24,17.76 24,15V8H0V15ZM5,14H8C8.55,14 9,14.45 9,15C9,15.55 8.55,16 8,16H5C4.45,16 4,15.55 4,15C4,14.45 4.45,14 5,14Z" + android:fillColor="#008DE4"/> diff --git a/wallet/res/drawable/ic_shortcut_receive.xml b/wallet/res/drawable/ic_shortcut_receive.xml deleted file mode 100644 index d618de294f..0000000000 --- a/wallet/res/drawable/ic_shortcut_receive.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - diff --git a/wallet/res/drawable/ic_shortcut_secure_now.xml b/wallet/res/drawable/ic_shortcut_secure_now.xml index 1b157dc60a..84c82a359c 100644 --- a/wallet/res/drawable/ic_shortcut_secure_now.xml +++ b/wallet/res/drawable/ic_shortcut_secure_now.xml @@ -1,24 +1,9 @@ + android:width="37dp" + android:height="36dp" + android:viewportWidth="37" + android:viewportHeight="36"> - - - - - - - - + android:pathData="M18.875,0C8.935,0 0.875,8.06 0.875,18C0.875,27.94 8.935,36 18.875,36C28.815,36 36.875,27.94 36.875,18C36.875,8.06 28.815,0 18.875,0ZM20.725,27.32V28.64C20.725,29.58 19.655,30.12 18.895,29.55L15.705,27.16C14.785,26.47 14.785,25.1 15.705,24.42L18.895,22.02C19.655,21.45 20.725,21.99 20.725,22.93V24.23C23.405,23.43 25.375,20.94 25.375,18C25.375,14.42 22.455,11.5 18.875,11.5C15.295,11.5 12.375,14.42 12.375,18C12.375,18.98 12.595,19.96 13.025,20.84C13.385,21.59 13.075,22.49 12.335,22.85C11.585,23.21 10.695,22.9 10.325,22.16C9.705,20.87 9.375,19.43 9.375,18C9.375,12.76 13.635,8.5 18.875,8.5C24.115,8.5 28.375,12.76 28.375,18C28.375,22.61 25.085,26.45 20.725,27.32Z" + android:fillColor="#78C4F5"/> diff --git a/wallet/res/drawable/ic_shortcut_staking.xml b/wallet/res/drawable/ic_shortcut_staking.xml new file mode 100644 index 0000000000..7d301a9792 --- /dev/null +++ b/wallet/res/drawable/ic_shortcut_staking.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/wallet/res/drawable/ic_shortcuts.xml b/wallet/res/drawable/ic_shortcuts.xml new file mode 100644 index 0000000000..271820bcbf --- /dev/null +++ b/wallet/res/drawable/ic_shortcuts.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/wallet/res/drawable/ic_where_to_spend.xml b/wallet/res/drawable/ic_where_to_spend.xml new file mode 100644 index 0000000000..8db8eb0113 --- /dev/null +++ b/wallet/res/drawable/ic_where_to_spend.xml @@ -0,0 +1,12 @@ + + + + diff --git a/wallet/res/layout/dialog_compose_container.xml b/wallet/res/layout/dialog_compose_container.xml index 1baa072f8a..c3caf4f4a8 100644 --- a/wallet/res/layout/dialog_compose_container.xml +++ b/wallet/res/layout/dialog_compose_container.xml @@ -14,13 +14,13 @@ android:layout_marginTop="8dp" tools:background="@color/light_gray" /> - - + + \ No newline at end of file diff --git a/wallet/res/layout/home_content.xml b/wallet/res/layout/home_content.xml index 8f23c79b61..cd47a00ab9 100644 --- a/wallet/res/layout/home_content.xml +++ b/wallet/res/layout/home_content.xml @@ -56,7 +56,7 @@ android:background="@color/background_primary" android:orientation="vertical" android:paddingStart="16dp" - android:paddingTop="54dp" + android:paddingTop="40dp" android:paddingEnd="16dp"> + @@ -127,10 +132,10 @@ - + + + + + + + + + + + مسح - إرسال + إرسال شراء & بيع diff --git a/wallet/res/values-bg/strings-extra.xml b/wallet/res/values-bg/strings-extra.xml index fc60682802..934ae33248 100644 --- a/wallet/res/values-bg/strings-extra.xml +++ b/wallet/res/values-bg/strings-extra.xml @@ -24,7 +24,7 @@ Сканирай - Изпрати + Изпрати Получаване diff --git a/wallet/res/values-cs/strings-extra.xml b/wallet/res/values-cs/strings-extra.xml index 5dfaeca41b..f8dc455a04 100644 --- a/wallet/res/values-cs/strings-extra.xml +++ b/wallet/res/values-cs/strings-extra.xml @@ -29,7 +29,7 @@ Skenovat - Poslat + Poslat Odeslat kontaktu diff --git a/wallet/res/values-de/strings-extra.xml b/wallet/res/values-de/strings-extra.xml index 84278f78a6..d3b89f3967 100644 --- a/wallet/res/values-de/strings-extra.xml +++ b/wallet/res/values-de/strings-extra.xml @@ -33,7 +33,7 @@ Scannen - Senden + Senden Zu einem Kontakt senden diff --git a/wallet/res/values-el/strings-extra.xml b/wallet/res/values-el/strings-extra.xml index c14517c061..5895346d24 100644 --- a/wallet/res/values-el/strings-extra.xml +++ b/wallet/res/values-el/strings-extra.xml @@ -33,7 +33,7 @@ Σάρωση - Αποστολή + Αποστολή Στείλτε στην επαφή diff --git a/wallet/res/values-es/strings-extra.xml b/wallet/res/values-es/strings-extra.xml index 7d0753ca33..db9d2202d5 100644 --- a/wallet/res/values-es/strings-extra.xml +++ b/wallet/res/values-es/strings-extra.xml @@ -33,7 +33,7 @@ Explorar - Enviar + Enviar Enviar a contacto diff --git a/wallet/res/values-fa/strings-extra.xml b/wallet/res/values-fa/strings-extra.xml index b03d740c58..eea286d905 100644 --- a/wallet/res/values-fa/strings-extra.xml +++ b/wallet/res/values-fa/strings-extra.xml @@ -33,7 +33,7 @@ اسکن - ارسال + ارسال ارسال برای مخاطب diff --git a/wallet/res/values-fil/strings-extra.xml b/wallet/res/values-fil/strings-extra.xml index 6a6a2b78bf..85672dc777 100644 --- a/wallet/res/values-fil/strings-extra.xml +++ b/wallet/res/values-fil/strings-extra.xml @@ -33,7 +33,7 @@ Scan - Ipadala + Ipadala Ipadala sa Contact diff --git a/wallet/res/values-fr/strings-extra.xml b/wallet/res/values-fr/strings-extra.xml index c01e2dd681..fa54e56674 100644 --- a/wallet/res/values-fr/strings-extra.xml +++ b/wallet/res/values-fr/strings-extra.xml @@ -33,7 +33,7 @@ Scanner - Envoyer + Envoyer Envoyer au contact diff --git a/wallet/res/values-he/strings.xml b/wallet/res/values-he/strings.xml index c959784782..27f901d7fe 100644 --- a/wallet/res/values-he/strings.xml +++ b/wallet/res/values-he/strings.xml @@ -24,9 +24,6 @@ נתקבל נשלח/נתקבל נשלח - לא נתקבלו ביטקוינים עד כה. - לא נשלחו ביטקוינים עד כה. - איך להשיג ביטקוין ?\n\nהמר תמורת כסף פיאט,\nמכור סחורות או שירותים או\nהשג על ידי עבודה. ברכותי, קיבלת את התשלום הראשון! האם כבר <u>גיבית את הארנק</u> כדי להגן מפני אבדן? נכרו פנימי @@ -37,13 +34,6 @@ נהל יישומים בדוק את הגדרות הזמנים שלך זמן המכשיר מאחר ב %d דקות. אתה כנראה לא יכול לקבל או לשלוח ביטקוינים בגלל בעיה זהו.\n\nכדאי שתבדוק את הגדרות הזמן ואיזור הזמן שלך. - גירסה חדשה זמינה! - גירסה זו מתקנת באגים חשובים, לפרטים גש לדף העדכון בGoogle Play. - אם אתה לא רואה עדכון, זה כנראה אומר שגרסאת האנדרואיד שלך אינה נתמכת יותר. - Google Play - הורדה - גירסת אנדרויד לא מעודכנת - יש סיכוי כי מהגרסאות הקרובות של ארנק ביטקוין לא נתמוך במכשירך יותר. במקרים מסוימים יהיה קשה להשתמש במטבעות על המכשיר.\n\nאלא אם אתה יודע מה אתה עושה, מומלץ שתעביר את המטבעות בקרוב. שלח ביטקוין אוסף חתימה מ%s… איסוף חתימה נכשל diff --git a/wallet/res/values-id/strings-extra.xml b/wallet/res/values-id/strings-extra.xml index d0690d0a16..2daae36546 100644 --- a/wallet/res/values-id/strings-extra.xml +++ b/wallet/res/values-id/strings-extra.xml @@ -33,7 +33,7 @@ Pindai - Kirim + Kirim Kirim ke Kontak diff --git a/wallet/res/values-it/strings-extra.xml b/wallet/res/values-it/strings-extra.xml index ae8a30c2b4..ddc9d9d396 100644 --- a/wallet/res/values-it/strings-extra.xml +++ b/wallet/res/values-it/strings-extra.xml @@ -33,7 +33,7 @@ Scansiona - Invia + Invia Invia al Contatto diff --git a/wallet/res/values-iw/strings.xml b/wallet/res/values-iw/strings.xml index d22d28cbee..f75c1b84b1 100644 --- a/wallet/res/values-iw/strings.xml +++ b/wallet/res/values-iw/strings.xml @@ -23,9 +23,6 @@ נתקבל נשלח/נתקבל נשלח - לא נתקבלו ביטקוינים עד כה. - לא נשלחו ביטקוינים עד כה. - איך להשיג ביטקוין ?\n\nהמר תמורת כסף פיאט,\nמכור סחורות או שירותים או\nהשג על ידי עבודה. ברכותי, קיבלת את התשלום הראשון! האם כבר <u>גיבית את הארנק</u> כדי להגן מפני אבדן? נכרו פנימי @@ -35,13 +32,6 @@ נהל יישומים בדוק את הגדרות הזמנים שלך זמן המכשיר מאחר ב %d דקות. אתה כנראה לא יכול לקבל או לשלוח ביטקוינים בגלל בעיה זהו.\n\nכדאי שתבדוק את הגדרות הזמן ואיזור הזמן שלך. - גירסה חדשה זמינה! - גירסה זו מתקנת באגים חשובים, לפרטים גש לדף העדכון בGoogle Play. - אם אתה לא רואה עדכון, זה כנראה אומר שגרסאת האנדרואיד שלך אינה נתמכת יותר. - Google Play - הורדה - גירסת אנדרויד לא מעודכנת - יש סיכוי כי מהגרסאות הקרובות של ארנק ביטקוין לא נתמוך במכשירך יותר. במקרים מסוימים יהיה קשה להשתמש במטבעות על המכשיר.\n\nאלא אם אתה יודע מה אתה עושה, מומלץ שתעביר את המטבעות בקרוב. שלח ביטקוין אוסף חתימה מ%s… איסוף חתימה נכשל diff --git a/wallet/res/values-ja/strings-extra.xml b/wallet/res/values-ja/strings-extra.xml index 61f3610b37..92783ba390 100644 --- a/wallet/res/values-ja/strings-extra.xml +++ b/wallet/res/values-ja/strings-extra.xml @@ -33,7 +33,7 @@ スキャン - 送金する + 送金する 連絡先に送金する diff --git a/wallet/res/values-ko/strings-extra.xml b/wallet/res/values-ko/strings-extra.xml index 2b31b53853..f98c31a43a 100644 --- a/wallet/res/values-ko/strings-extra.xml +++ b/wallet/res/values-ko/strings-extra.xml @@ -33,7 +33,7 @@ 스캔 - 보내기 + 보내기 연락처에 전송하기 diff --git a/wallet/res/values-nl/strings-extra.xml b/wallet/res/values-nl/strings-extra.xml index f31ea04a91..7c9794f810 100644 --- a/wallet/res/values-nl/strings-extra.xml +++ b/wallet/res/values-nl/strings-extra.xml @@ -33,7 +33,7 @@ Zoeken - Verzenden + Verzenden Stuur naar contact diff --git a/wallet/res/values-pl/strings-extra.xml b/wallet/res/values-pl/strings-extra.xml index 122fb6ebf1..6a95728ba7 100644 --- a/wallet/res/values-pl/strings-extra.xml +++ b/wallet/res/values-pl/strings-extra.xml @@ -33,7 +33,7 @@ Zeskanuj - Wyślij + Wyślij Wyślij do Kontaktu diff --git a/wallet/res/values-pt/strings-extra.xml b/wallet/res/values-pt/strings-extra.xml index 82e7d305b7..a6f108a3a9 100644 --- a/wallet/res/values-pt/strings-extra.xml +++ b/wallet/res/values-pt/strings-extra.xml @@ -33,7 +33,7 @@ Escanear - Enviar + Enviar Enviar para contato diff --git a/wallet/res/values-ro/strings-extra.xml b/wallet/res/values-ro/strings-extra.xml index 25f76c2942..58611f049e 100644 --- a/wallet/res/values-ro/strings-extra.xml +++ b/wallet/res/values-ro/strings-extra.xml @@ -8,7 +8,7 @@ Scanează - Trimite + Trimite Importă Cheia Privată diff --git a/wallet/res/values-ru/strings-extra.xml b/wallet/res/values-ru/strings-extra.xml index 5d8bce413a..4359db69e5 100644 --- a/wallet/res/values-ru/strings-extra.xml +++ b/wallet/res/values-ru/strings-extra.xml @@ -33,7 +33,7 @@ Скан. - Отправить + Отправить Отправить человеку в списке контактов @@ -450,7 +450,7 @@ Ключи голосования Ключи оператора Ключи идентификаторов Evolution нод - ключи + %d ключей %d использовано Ключи %d Адрес diff --git a/wallet/res/values-sk/strings-extra.xml b/wallet/res/values-sk/strings-extra.xml index 29666624a1..aa6c4ec147 100644 --- a/wallet/res/values-sk/strings-extra.xml +++ b/wallet/res/values-sk/strings-extra.xml @@ -33,7 +33,7 @@ Skenovať - Poslať + Poslať Poslať kontaktu diff --git a/wallet/res/values-sr/strings-extra.xml b/wallet/res/values-sr/strings-extra.xml index 13ce664423..aad7315df5 100644 --- a/wallet/res/values-sr/strings-extra.xml +++ b/wallet/res/values-sr/strings-extra.xml @@ -8,7 +8,7 @@ Skeniraj - Pošalji + Pošalji Uvezi privatni ključ diff --git a/wallet/res/values-sw/strings.xml b/wallet/res/values-sw/strings.xml index b33d4b7fc4..9335b76ab6 100644 --- a/wallet/res/values-sw/strings.xml +++ b/wallet/res/values-sw/strings.xml @@ -25,9 +25,6 @@ Kupokea Wote Alimtuma - Hakuna Dash kupokea hivyo mbali - Hakuna Dash kutumwa hivyo mbali - Jinsi ya bamba Dash? Nunua na pesa ya kawaida, na biashara, na kazi. Pongezi budha! Wewe kupokea pesa yako ya kwanza! Lazima kubamba backup, Huna wanataka kupata kuiba. kupatikana ndani @@ -40,13 +37,6 @@ Kudhibiti programu Cheki wazingira wakati yako! Simu yako wakati ni makosa %d minuti. Pengine hawezi kutuma Dash kwa sababu ya tatizo hili. Angalia mipangilio yako majira ya saa. - Toleo jipya ni hapa kwa shusha! - Hii toleo kunasa makosa makubwa. Kwa maelezo, kuona changelog juu ya Google Play. - Kama huwezi kupata toleo jipya, Android yako ni mzee, sana! - Google Play - Shusha - Android yako ni mzee, sana! - Matoleo ya pili ya pochi hii si msaada Android yako yoyote zaidi. Katika baadhi ya matukio, matumizi ya pesa itakuwa vigumu. Kama wewe ni uhakika, tafadhali kuchukua pesa yako hivi karibuni. Kutuma Dash Kupata sahihi ya %s… Kupata sahihi alishindwa. diff --git a/wallet/res/values-th/strings-extra.xml b/wallet/res/values-th/strings-extra.xml index 44fb4ceb98..6ea91912d2 100644 --- a/wallet/res/values-th/strings-extra.xml +++ b/wallet/res/values-th/strings-extra.xml @@ -31,7 +31,7 @@ สแกน - ส่ง + ส่ง ส่งไปยังผู้ติดต่อ diff --git a/wallet/res/values-tr/strings-extra.xml b/wallet/res/values-tr/strings-extra.xml index 84fd073bc8..c98a79cf7d 100644 --- a/wallet/res/values-tr/strings-extra.xml +++ b/wallet/res/values-tr/strings-extra.xml @@ -31,7 +31,7 @@ Tara - Gönder + Gönder Kişiye Gönder diff --git a/wallet/res/values-uk/strings-extra.xml b/wallet/res/values-uk/strings-extra.xml index 6f05d5ac0f..f7e765f15e 100644 --- a/wallet/res/values-uk/strings-extra.xml +++ b/wallet/res/values-uk/strings-extra.xml @@ -33,7 +33,7 @@ Сканувати - Відправити + Відправити Надіслати контакту diff --git a/wallet/res/values-vi/strings-extra.xml b/wallet/res/values-vi/strings-extra.xml index 5cd2514bbd..692f8b045f 100644 --- a/wallet/res/values-vi/strings-extra.xml +++ b/wallet/res/values-vi/strings-extra.xml @@ -25,7 +25,7 @@ Quét - Gửi + Gửi Nhận diff --git a/wallet/res/values-zh-rTW/strings-extra.xml b/wallet/res/values-zh-rTW/strings-extra.xml index b38e0d3249..ba29319280 100644 --- a/wallet/res/values-zh-rTW/strings-extra.xml +++ b/wallet/res/values-zh-rTW/strings-extra.xml @@ -33,7 +33,7 @@ 掃描 - 付款 + 付款 發送給聯繫人 diff --git a/wallet/res/values-zh/strings-extra.xml b/wallet/res/values-zh/strings-extra.xml index af9a3a64ae..503ad89657 100644 --- a/wallet/res/values-zh/strings-extra.xml +++ b/wallet/res/values-zh/strings-extra.xml @@ -33,7 +33,7 @@ 扫描 - 发送 + 发送 发送给联系人 diff --git a/wallet/res/values/strings-extra.xml b/wallet/res/values/strings-extra.xml index ada20a60f7..febe4bcc13 100644 --- a/wallet/res/values/strings-extra.xml +++ b/wallet/res/values/strings-extra.xml @@ -31,9 +31,9 @@ Backup - Scan + Scan QR - Send + Send Send to Contact @@ -42,8 +42,6 @@ Receive Import Private Key - - Add Shortcut Quick Receive @@ -219,7 +217,7 @@ Dash Core Group does NOT store this recovery phrase Anyone that has your recovery phrase can access your funds. You will NOT be able to restore the wallet without a recovery phrase - Write it in a safe place and don’t show it to anyone. + Write it in a safe place and don\'t show it to anyone. Please tap on the words from your recovery phrase in the right order Verified Successfully Your wallet is secured now. You can use your recovery phrase anytime to recover your account on another device. diff --git a/wallet/res/values/strings.xml b/wallet/res/values/strings.xml index 70391ebef2..adcb40020b 100644 --- a/wallet/res/values/strings.xml +++ b/wallet/res/values/strings.xml @@ -420,4 +420,9 @@ Update profile information Select block explorer + + + Select option + Customize shortcut bar + Hold any button above to replace it with the function you need diff --git a/wallet/schnapps/res/values/values.xml b/wallet/schnapps/res/values/values.xml index d891f07098..0a4337be59 100644 --- a/wallet/schnapps/res/values/values.xml +++ b/wallet/schnapps/res/values/values.xml @@ -20,8 +20,10 @@ https://insight.ouzo.networks.dash.org:3002/insight + https://blockchair.com/dash/ Insight + Blockchair \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/di/AppModule.kt b/wallet/src/de/schildbach/wallet/di/AppModule.kt index fb2690f287..de695f9d6d 100644 --- a/wallet/src/de/schildbach/wallet/di/AppModule.kt +++ b/wallet/src/de/schildbach/wallet/di/AppModule.kt @@ -95,7 +95,7 @@ abstract class AppModule { @Provides fun provideDeviceInfo(@ApplicationContext context: Context): DeviceInfoProvider = - DeviceInfoProvider(context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager) + DeviceInfoProvider(context.resources, context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager) @Singleton @Provides diff --git a/wallet/src/de/schildbach/wallet/service/DeviceInfoProvider.kt b/wallet/src/de/schildbach/wallet/service/DeviceInfoProvider.kt index 0d30e3bb42..535fbbf0fa 100644 --- a/wallet/src/de/schildbach/wallet/service/DeviceInfoProvider.kt +++ b/wallet/src/de/schildbach/wallet/service/DeviceInfoProvider.kt @@ -1,15 +1,21 @@ package de.schildbach.wallet.service +import android.content.res.Resources import android.telephony.TelephonyManager +import android.util.DisplayMetrics import org.slf4j.LoggerFactory class DeviceInfoProvider( + private val resources: Resources, private val telephonyManager: TelephonyManager ) { companion object { private val log = LoggerFactory.getLogger(DeviceInfoProvider::class.java) } + val isSmallScreen: Boolean + get() = resources.displayMetrics.densityDpi <= DisplayMetrics.DENSITY_MEDIUM + /** * Get ISO 3166-1 alpha-2 country code for this device (or null if not available) * If available, call [.showFiatCurrencyChangeDetectedDialog] diff --git a/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt b/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt index 1434b96462..9b2e96e8fe 100644 --- a/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt +++ b/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt @@ -18,7 +18,6 @@ package de.schildbach.wallet.transactions import de.schildbach.wallet.Constants -import de.schildbach.wallet.data.CoinJoinConfig import de.schildbach.wallet.service.CoinJoinMode import de.schildbach.wallet.service.CoinJoinService import de.schildbach.wallet.util.ThrottlingWalletChangeListener @@ -41,7 +40,6 @@ import org.bitcoinj.wallet.Wallet.BalanceType import org.dash.wallet.common.data.WalletUIConfig import org.slf4j.LoggerFactory - class WalletBalanceObserver( private val wallet: Wallet, private val walletUIConfig: WalletUIConfig diff --git a/wallet/src/de/schildbach/wallet/ui/compose_views/ComposeBottomSheet.kt b/wallet/src/de/schildbach/wallet/ui/compose_views/ComposeBottomSheet.kt index 54f33a6b47..fb5b7342ee 100644 --- a/wallet/src/de/schildbach/wallet/ui/compose_views/ComposeBottomSheet.kt +++ b/wallet/src/de/schildbach/wallet/ui/compose_views/ComposeBottomSheet.kt @@ -11,6 +11,7 @@ import org.dash.wallet.common.ui.viewBinding class ComposeBottomSheet( override val backgroundStyle: Int = R.style.SecondaryBackground, + override val forceExpand: Boolean = false, private val content: @Composable (DialogFragment) -> Unit ) : OffsetDialogFragment(R.layout.dialog_compose_container) { private val binding by viewBinding(DialogComposeContainerBinding::bind) diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/FrequentContactViewHolder.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/FrequentContactViewHolder.kt index 36269a6d20..6e9e2d050d 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/FrequentContactViewHolder.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/FrequentContactViewHolder.kt @@ -15,12 +15,9 @@ */ package de.schildbach.wallet.ui.dashpay -import android.view.LayoutInflater -import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import de.schildbach.wallet.data.UsernameSearchResult import de.schildbach.wallet.ui.dashpay.utils.display -import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.FrequentContactItemBinding import org.dash.wallet.common.ui.avatar.ProfilePictureDisplay diff --git a/wallet/src/de/schildbach/wallet/ui/main/HeaderBalanceFragment.kt b/wallet/src/de/schildbach/wallet/ui/main/HeaderBalanceFragment.kt index b406bb17c1..f9757a291c 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/HeaderBalanceFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/main/HeaderBalanceFragment.kt @@ -31,6 +31,7 @@ import de.schildbach.wallet_test.databinding.HeaderBalanceFragmentBinding import org.bitcoinj.core.Coin import org.dash.wallet.common.ui.viewBinding import org.dash.wallet.common.util.GenericUtils +import org.dash.wallet.common.util.observe class HeaderBalanceFragment : Fragment(R.layout.header_balance_fragment) { private val viewModel by activityViewModels() @@ -66,7 +67,7 @@ class HeaderBalanceFragment : Fragment(R.layout.header_balance_fragment) { } viewModel.showTapToHideHint.observe(viewLifecycleOwner) { showHint -> - binding.hideBalanceHintText.isVisible = showHint ?: true + binding.hideBalanceHintText.isVisible = showHint != false } } diff --git a/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt b/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt index 7470d30c10..d653204955 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt @@ -65,13 +65,17 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import org.bitcoinj.core.Coin import org.bitcoinj.core.Context @@ -217,17 +221,18 @@ class MainViewModel @Inject constructor( .combine(_temporaryHideBalance) { autoHide, temporaryHide -> temporaryHide ?: autoHide ?: false } - .asLiveData() + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = false + ) - val showTapToHideHint = walletUIConfig.observe(WalletUIConfig.SHOW_TAP_TO_HIDE_HINT).asLiveData() + val showTapToHideHint = walletUIConfig.observe(WalletUIConfig.SHOW_TAP_TO_HIDE_HINT) private val _isNetworkUnavailable = MutableLiveData() val isNetworkUnavailable: LiveData get() = _isNetworkUnavailable - val isPassphraseVerified: Boolean - get() = !config.remindBackupSeed - val currencyChangeDetected = SingleLiveEvent>() // CoinJoin @@ -341,11 +346,15 @@ class MainViewModel @Inject constructor( // we need the total wallet balance for mixing progress, walletData.observeTotalBalance() - .onEach(_totalBalance::postValue) + .onEach { + _totalBalance.value = it + } .launchIn(viewModelScope) walletData.observeMixedBalance() - .onEach(_mixedBalance::postValue) + .onEach { + _mixedBalance.value = it + } .launchIn(viewModelScope) walletUIConfig @@ -409,8 +418,7 @@ class MainViewModel @Inject constructor( } fun triggerHideBalance() { - val isHiding = hideBalance.value ?: false - _temporaryHideBalance.value = !isHiding + _temporaryHideBalance.value = !hideBalance.value if (_temporaryHideBalance.value == true) { logEvent(AnalyticsConstants.Home.HIDE_BALANCE) @@ -459,7 +467,7 @@ class MainViewModel @Inject constructor( coinJoinService.updateTimeSkew(timeSkew) log.info("timeskew: {} ms", timeSkew) return Pair(abs(timeSkew) > maxAllowedTimeSkew, timeSkew) - } catch (ex: Exception) { + } catch (_: Exception) { // Ignore errors Pair(false, 0) } @@ -571,7 +579,7 @@ class MainViewModel @Inject constructor( } if (included && wrapper != null) { - itemId = wrapper!!.id + itemId = wrapper.id } else { this.crowdNodeWrapperFactory.tryInclude(tx).also { included = it.first @@ -579,7 +587,7 @@ class MainViewModel @Inject constructor( } if (included && wrapper != null) { - itemId = wrapper!!.id + itemId = wrapper.id } } } @@ -599,7 +607,7 @@ class MainViewModel @Inject constructor( ) } else { TransactionRowView.fromTransactionWrapper( - wrapper!!, + wrapper, walletData.transactionBag, Constants.CONTEXT, null, diff --git a/wallet/src/de/schildbach/wallet/ui/main/WalletFragment.kt b/wallet/src/de/schildbach/wallet/ui/main/WalletFragment.kt index b9839f2b53..2e23d19f6b 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/WalletFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/main/WalletFragment.kt @@ -18,15 +18,23 @@ package de.schildbach.wallet.ui.main import android.app.Activity -import androidx.fragment.app.activityViewModels import android.content.Intent import android.os.Bundle import android.view.View import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.net.toUri import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.NavOptions import androidx.navigation.fragment.findNavController @@ -35,19 +43,27 @@ import com.google.android.material.appbar.AppBarLayout.Behavior.DragCallback import com.google.android.material.transition.MaterialFadeThrough import dagger.hilt.android.AndroidEntryPoint import de.schildbach.wallet.data.PaymentIntent +import de.schildbach.wallet.data.ServiceType import de.schildbach.wallet.service.CoinJoinMode import de.schildbach.wallet.service.MixingStatus -import de.schildbach.wallet.ui.* -import de.schildbach.wallet.ui.util.InputParser.StringInputParser +import de.schildbach.wallet.ui.EditProfileActivity +import de.schildbach.wallet.ui.LockScreenActivity +import de.schildbach.wallet.ui.compose_views.ComposeBottomSheet import de.schildbach.wallet.ui.dashpay.ContactsScreenMode import de.schildbach.wallet.ui.dashpay.NotificationsFragment import de.schildbach.wallet.ui.dashpay.utils.display +import de.schildbach.wallet.ui.main.shortcuts.ShortcutOption +import de.schildbach.wallet.ui.main.shortcuts.ShortcutsList +import de.schildbach.wallet.ui.main.shortcuts.ShortcutsPane +import de.schildbach.wallet.ui.main.shortcuts.ShortcutsViewModel import de.schildbach.wallet.ui.payments.PaymentsFragment import de.schildbach.wallet.ui.payments.SweepWalletActivity import de.schildbach.wallet.ui.scan.ScanActivity import de.schildbach.wallet.ui.send.SendCoinsActivity +import de.schildbach.wallet.ui.staking.StakingActivity import de.schildbach.wallet.ui.transactions.TaxCategoryExplainerDialogFragment import de.schildbach.wallet.ui.transactions.TransactionDetailsDialogFragment +import de.schildbach.wallet.ui.util.InputParser.StringInputParser import de.schildbach.wallet.ui.verify.VerifySeedActivity import de.schildbach.wallet.util.WalletUtils import de.schildbach.wallet_test.R @@ -62,10 +78,14 @@ import org.dash.wallet.common.Configuration import org.dash.wallet.common.services.AuthenticationManager import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.ui.avatar.ProfilePictureDisplay +import org.dash.wallet.common.ui.components.InfoPanel import org.dash.wallet.common.ui.dialogs.AdaptiveDialog import org.dash.wallet.common.ui.viewBinding +import org.dash.wallet.common.util.Constants import org.dash.wallet.common.util.observe +import org.dash.wallet.common.util.openCustomTab import org.dash.wallet.common.util.safeNavigate +import org.dash.wallet.features.exploredash.ui.explore.ExploreTopic import org.slf4j.LoggerFactory import javax.inject.Inject @@ -77,6 +97,7 @@ class WalletFragment : Fragment(R.layout.home_content) { } private val viewModel: MainViewModel by activityViewModels() + private val shortcutViewModel: ShortcutsViewModel by activityViewModels() private val binding by viewBinding(HomeContentBinding::bind) private lateinit var mixingBinding: MixingStatusPaneBinding @Inject lateinit var configuration: Configuration @@ -93,6 +114,14 @@ class WalletFragment : Fragment(R.layout.home_content) { } } + private val stakingLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode == Constants.USER_BUY_SELL_DASH) { + safeNavigate(WalletFragmentDirections.homeToBuySell()) + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -130,6 +159,27 @@ class WalletFragment : Fragment(R.layout.home_content) { startActivity(Intent(requireContext(), EditProfileActivity::class.java)) } + binding.infoPanel.setViewCompositionStrategy( + ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed + ) + + binding.infoPanel.setContent { + if (shortcutViewModel.showShortcutInfo) { + InfoPanel( + stringResource(R.string.customize_shortcuts), + stringResource(R.string.customize_shortcuts_description), + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(horizontal = 2.dp), + leftIconRes = R.drawable.ic_shortcuts, + actionIconRes = R.drawable.ic_popup_close + ) { + shortcutViewModel.hideShortcutInfo() + } + } + } + viewModel.transactions.observe(viewLifecycleOwner) { refreshShortcutBar() } viewModel.isBlockchainSynced.observe(viewLifecycleOwner) { updateSyncState() } viewModel.isBlockchainSyncFailed.observe(viewLifecycleOwner) { updateSyncState() } @@ -213,6 +263,8 @@ class WalletFragment : Fragment(R.layout.home_content) { viewModel.totalBalance.observe(viewLifecycleOwner) { updateMixedAndTotalBalance() + val balance: Coin = viewModel.totalBalance.value ?: Coin.ZERO + shortcutViewModel.userHasBalance = balance.isPositive } viewModel.mixedBalance.observe(viewLifecycleOwner) { @@ -245,75 +297,32 @@ class WalletFragment : Fragment(R.layout.home_content) { override fun onResume() { super.onResume() - showHideSecureAction() + shortcutViewModel.refreshIsPassphraseVerified() } private fun initShortcutActions() { - binding.shortcutsPane.setOnShortcutClickListener { v -> - when (v) { - binding.shortcutsPane.secureNowButton -> { - viewModel.logEvent(AnalyticsConstants.Home.SHORTCUT_SECURE_WALLET) - handleVerifySeed() - } - binding.shortcutsPane.scanToPayButton -> { - viewModel.logEvent(AnalyticsConstants.Home.SHORTCUT_SCAN_TO_PAY) - handleScan(v) - } - binding.shortcutsPane.buySellButton -> { - viewModel.logEvent(AnalyticsConstants.Home.SHORTCUT_BUY_AND_SELL) - safeNavigate(WalletFragmentDirections.homeToBuySell()) - } - binding.shortcutsPane.payToAddressButton -> { - handlePayToAddress() - } - binding.shortcutsPane.payToContactButton -> { - handleSelectContact() - } - binding.shortcutsPane.receiveButton -> { - viewModel.logEvent(AnalyticsConstants.Home.SHORTCUT_RECEIVE) - findNavController().navigate( - R.id.paymentsFragment, - bundleOf( - PaymentsFragment.ARG_ACTIVE_TAB to PaymentsFragment.ACTIVE_TAB_RECEIVE - ) - ) - } - binding.shortcutsPane.importPrivateKey -> { - SweepWalletActivity.start(requireContext(), true) - } - binding.shortcutsPane.explore -> { - viewModel.logEvent(AnalyticsConstants.Home.SHORTCUT_EXPLORE) - findNavController().navigate( - R.id.exploreFragment, - bundleOf(), - NavOptions.Builder() - .setEnterAnim(R.anim.slide_in_bottom) - .build() - ) + binding.shortcutsPane.setViewCompositionStrategy( + ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed + ) + + binding.shortcutsPane.setContent { + ShortcutsPane( + shortcuts = shortcutViewModel.shortcuts, + onClick = { shortcut -> + onShortcutTap(shortcut) + }, + onLongClick = { shortcut, index -> + onShortcutLongTap(shortcut, index) } - } + ) } refreshShortcutBar() } private fun refreshShortcutBar() { - showHideSecureAction() - refreshIfUserHasBalance() - refreshIfUserHasIdentity() - } - - private fun showHideSecureAction() { - binding.shortcutsPane.isPassphraseVerified = viewModel.isPassphraseVerified - } - - private fun refreshIfUserHasBalance() { - val balance: Coin = viewModel.totalBalance.value ?: Coin.ZERO - binding.shortcutsPane.userHasBalance = balance.isPositive - } - - private fun refreshIfUserHasIdentity() { - binding.shortcutsPane.userHasContacts = viewModel.hasIdentity && viewModel.hasContacts.value + shortcutViewModel.refreshIsPassphraseVerified() + shortcutViewModel.userHasContacts = viewModel.hasIdentity && viewModel.hasContacts.value } private fun updateSyncState() { @@ -339,15 +348,9 @@ class WalletFragment : Fragment(R.layout.home_content) { } } - private fun handleScan(clickView: View?) { - if (clickView != null) { - val options = ScanActivity.getLaunchOptions(activity, clickView) - val intent = ScanActivity.getTransitionIntent(activity, clickView) - scanLauncher.launch(intent, options) - } else { - val intent = ScanActivity.getIntent(activity) - scanLauncher.launch(intent) - } + private fun handleScan() { + val intent = ScanActivity.getIntent(activity) + scanLauncher.launch(intent) } private fun startVerifySeedActivity(pin: String) { @@ -418,4 +421,116 @@ class WalletFragment : Fragment(R.layout.home_content) { } ) } + + private fun onShortcutTap(shortcut: ShortcutOption) { + when (shortcut) { + ShortcutOption.SECURE_NOW -> { + viewModel.logEvent(AnalyticsConstants.Home.SHORTCUT_SECURE_WALLET) + handleVerifySeed() + } + ShortcutOption.SCAN_QR -> { + viewModel.logEvent(AnalyticsConstants.Home.SHORTCUT_SCAN_TO_PAY) + handleScan() + } + ShortcutOption.BUY_SELL -> { + viewModel.logEvent(AnalyticsConstants.Home.SHORTCUT_BUY_AND_SELL) + safeNavigate(WalletFragmentDirections.homeToBuySell()) + } + ShortcutOption.SEND_TO_ADDRESS -> { + handlePayToAddress() + } + ShortcutOption.SEND_TO_CONTACT -> { + handleSelectContact() + } + ShortcutOption.SEND -> { + viewModel.logEvent(AnalyticsConstants.Home.SHORTCUT_SEND) + findNavController().navigate( + R.id.paymentsFragment, + bundleOf( + PaymentsFragment.ARG_ACTIVE_TAB to PaymentsFragment.ACTIVE_TAB_PAY + ) + ) + } + ShortcutOption.RECEIVE -> { + viewModel.logEvent(AnalyticsConstants.Home.SHORTCUT_RECEIVE) + findNavController().navigate( + R.id.paymentsFragment, + bundleOf( + PaymentsFragment.ARG_ACTIVE_TAB to PaymentsFragment.ACTIVE_TAB_RECEIVE + ) + ) + } + ShortcutOption.EXPLORE -> { + viewModel.logEvent(AnalyticsConstants.Home.SHORTCUT_EXPLORE) + findNavController().navigate( + R.id.exploreFragment, + bundleOf(), + NavOptions.Builder() + .setEnterAnim(R.anim.slide_in_bottom) + .build() + ) + } + ShortcutOption.WHERE_TO_SPEND -> { + safeNavigate(WalletFragmentDirections.homeToSearch(type = ExploreTopic.Merchants)) + } + ShortcutOption.ATMS -> { + safeNavigate(WalletFragmentDirections.homeToSearch(type = ExploreTopic.ATMs)) + } + ShortcutOption.STAKING -> { + handleStakingNavigation() + } + ShortcutOption.TOPPER -> { + lifecycleScope.launch { + val uri = shortcutViewModel.getTopperUrl(getString(R.string.dash_wallet_name)) + requireActivity().openCustomTab(uri) + } + } + ShortcutOption.UPHOLD -> { + safeNavigate(WalletFragmentDirections.homeToUphold()) + } + ShortcutOption.COINBASE -> { + if (shortcutViewModel.isCoinbaseAuthenticated) { + safeNavigate(WalletFragmentDirections.homeToCoinbase()) + } else { + safeNavigate(WalletFragmentDirections.homeToBuySellOverview(ServiceType.COINBASE)) + } + } + } + } + + private fun onShortcutLongTap(shortcut: ShortcutOption, index: Int) { + if (shortcut == ShortcutOption.SECURE_NOW) { + return + } + + ComposeBottomSheet(R.style.SecondaryBackground, forceExpand = true) { dialog -> + ShortcutsList(shortcutViewModel.getAllShortcutOptions(shortcut)) { newShortcut -> + shortcutViewModel.replaceShortcut(index, newShortcut) + dialog.dismiss() + } + }.show(requireActivity()) + + shortcutViewModel.hideShortcutInfo() // Assume user is aware of this feature already + } + + private fun handleStakingNavigation() { + lifecycleScope.launch { + if (viewModel.isBlockchainSynced.value == true) { + stakingLauncher.launch(Intent(requireContext(), StakingActivity::class.java)) + } else { + val openWebsite = AdaptiveDialog.create( + null, + getString(R.string.chain_syncing), + getString(R.string.crowdnode_wait_for_sync), + getString(R.string.button_close), + getString(R.string.crowdnode_open_website) + ).showAsync(requireActivity()) + + if (openWebsite == true) { + val browserIntent = Intent(Intent.ACTION_VIEW, getString(R.string.crowdnode_website).toUri()) + startActivity(browserIntent) + } + } + } + } } diff --git a/wallet/src/de/schildbach/wallet/ui/main/shortcuts/Shortcut.kt b/wallet/src/de/schildbach/wallet/ui/main/shortcuts/Shortcut.kt new file mode 100644 index 0000000000..c3ae529323 --- /dev/null +++ b/wallet/src/de/schildbach/wallet/ui/main/shortcuts/Shortcut.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2025 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 de.schildbach.wallet.ui.main.shortcuts + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.dash.wallet.common.ui.components.MyTheme + +@Composable +fun Shortcut( + shortcutOption: ShortcutOption, + modifier: Modifier = Modifier +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Top, + modifier = modifier + ) { + Image( + painter = painterResource(id = shortcutOption.iconResId), + contentDescription = stringResource(id = shortcutOption.textResId), + modifier = Modifier + .size(42.dp) + .padding(6.dp) + ) + + Text( + text = stringResource(id = shortcutOption.textResId), + style = MyTheme.Overline, + color = MyTheme.Colors.textPrimary, + textAlign = TextAlign.Center, + modifier = Modifier + .padding(top = 4.dp, bottom = 6.dp) + ) + } +} + +@Preview(showBackground = true) +@Composable +fun PreviewShortcutPaneItem() { + Shortcut( + shortcutOption = ShortcutOption.RECEIVE + ) +} \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutListItem.kt b/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutListItem.kt new file mode 100644 index 0000000000..7b6f445199 --- /dev/null +++ b/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutListItem.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2025 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 de.schildbach.wallet.ui.main.shortcuts + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.dash.wallet.common.ui.components.MyTheme + +@Composable +fun ShortcutListItem( + shortcutOption: ShortcutOption, + onClick: (ShortcutOption) -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 10.dp, vertical = 2.dp) + .clip(RoundedCornerShape(8.dp)) + .background(MyTheme.Colors.backgroundSecondary) + .clickable { onClick(shortcutOption) } + .padding(horizontal = 10.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(26.dp), + contentAlignment = Alignment.Center + ) { + Image( + painter = painterResource(id = shortcutOption.iconResId), + contentDescription = stringResource(id = shortcutOption.textResId), + modifier = Modifier.size(24.dp) + ) + } + + Text( + text = stringResource(id = shortcutOption.textResId), + style = MyTheme.Body2Regular, + color = MyTheme.Colors.textPrimary, + modifier = Modifier + .weight(1f) + .padding(start = 16.dp) + ) + } +} + +@Preview(showBackground = true) +@Composable +fun PreviewShortcutListItem() { + ShortcutListItem( + shortcutOption = ShortcutOption.RECEIVE, + onClick = {} + ) +} \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutOption.kt b/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutOption.kt new file mode 100644 index 0000000000..da422cd865 --- /dev/null +++ b/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutOption.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2025 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 de.schildbach.wallet.ui.main.shortcuts + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import de.schildbach.wallet_test.R + +// Sorted by display priority. Do not change IDs if moving options around +enum class ShortcutOption( + val id: Int, + @DrawableRes val iconResId: Int, + @StringRes val textResId: Int +) { + SECURE_NOW( + 0, + R.drawable.ic_shortcut_secure_now, + R.string.shortcut_secure_now + ), + EXPLORE( + 1, + R.drawable.ic_explore, + R.string.menu_explore_title + ), + RECEIVE( + 2, + R.drawable.ic_transaction_received, + R.string.shortcut_receive + ), + SEND( + 3, + R.drawable.ic_transaction_sent, + R.string.shortcut_send + ), + SCAN_QR( + 4, + R.drawable.ic_qr, + R.string.shortcut_scan_to_pay + ), + SEND_TO_ADDRESS( + 5, + R.drawable.ic_send_to_address, + R.string.send_to_address + ), + SEND_TO_CONTACT( + 6, + R.drawable.ic_contact, + R.string.shortcut_pay_to_contact + ), + BUY_SELL( + 7, + R.drawable.ic_shortcut_buy_sell_dash, + R.string.shortcut_buy_sell + ), + WHERE_TO_SPEND( + 8, + R.drawable.ic_where_to_spend, + R.string.explore_where_to_spend + ), + ATMS( + 9, + R.drawable.ic_shortcut_atm, + R.string.explore_atms + ), + STAKING( + 10, + R.drawable.ic_shortcut_staking, + R.string.staking_title + ), + TOPPER( + 11, + R.drawable.logo_topper, + R.string.topper + ), + UPHOLD( + 12, + R.drawable.ic_uphold, + R.string.uphold_account + ), + COINBASE( + 13, + R.drawable.ic_coinbase, + R.string.coinbase + ); + + companion object { + fun fromId(id: Int): ShortcutOption { + return entries.first { it.id == id } + } + } +} \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutProvider.kt b/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutProvider.kt new file mode 100644 index 0000000000..f00500777a --- /dev/null +++ b/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutProvider.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2025 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 de.schildbach.wallet.ui.main.shortcuts + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import org.dash.wallet.common.data.WalletUIConfig +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ShortcutProvider @Inject constructor( + private val config: WalletUIConfig +) { + companion object { + // Normally, there should be at least 4 custom shortcuts saved + private const val MINIMUM_SHORTCUTS = 4 + } + + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + private val _customShortcuts = MutableStateFlow>(emptyList()) + val customShortcuts: StateFlow> = _customShortcuts.asStateFlow() + + init { + config.observe(WalletUIConfig.CUSTOMIZED_SHORTCUTS) + .distinctUntilChanged() + .map { shortcutSet -> if (shortcutSet == null) null else parseCustomShortcuts(shortcutSet) } + .onEach { shortcuts -> + if (shortcuts == null) { + // DataStore was cleared out, probably due to a reset + _customShortcuts.value = emptyList() + return@onEach + } + + var finalShortcuts = shortcuts.toMutableList() + + if (finalShortcuts.size < MINIMUM_SHORTCUTS) { + val allShortcuts = ShortcutOption.entries + allShortcuts + .firstOrNull { it != ShortcutOption.SECURE_NOW && it !in finalShortcuts } + // Most likely short 1 item due to removal from the start of the list + ?.let { finalShortcuts.add(0, it) } + setCustomShortcuts(finalShortcuts.map { it.id }.toIntArray()) // This will trigger another pass + } else { + _customShortcuts.value = finalShortcuts + } + } + .launchIn(scope) + } + + // Default logic before the user customizes shortcuts + fun getFilteredShortcuts( + isPassphraseVerified: Boolean = true, + userHasBalance: Boolean = true, + userHasContacts: Boolean = false + ): List { + val shortcuts = ShortcutOption.entries.filter { shortcut -> + when (shortcut) { + ShortcutOption.SECURE_NOW -> !isPassphraseVerified + ShortcutOption.SCAN_QR -> userHasBalance + ShortcutOption.SEND -> !userHasBalance && isPassphraseVerified + ShortcutOption.BUY_SELL -> !userHasBalance + ShortcutOption.SEND_TO_ADDRESS -> userHasBalance + ShortcutOption.SEND_TO_CONTACT -> userHasBalance && userHasContacts + else -> true + } + } + + return shortcuts + } + + suspend fun setCustomShortcuts(shortcutIds: IntArray) { + val shortcutString = shortcutIds.joinToString(",") { it.toString() } + config.set(WalletUIConfig.CUSTOMIZED_SHORTCUTS, shortcutString) + } + + private fun parseCustomShortcuts(shortcutString: String): List { + return shortcutString.split(",").mapNotNull { idStr -> + val id = idStr.toIntOrNull() ?: return@mapNotNull null + ShortcutOption.fromId(id) + } + } +} \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutsList.kt b/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutsList.kt new file mode 100644 index 0000000000..7e3e5df6c1 --- /dev/null +++ b/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutsList.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2025 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 de.schildbach.wallet.ui.main.shortcuts + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import de.schildbach.wallet_test.R +import org.dash.wallet.common.ui.components.MyTheme.Colors + +/** + * A Composable that displays a list of shortcut options. + * + * @param shortcuts The list of shortcut options to display + * @param onClick Callback to execute when a shortcut is clicked + */ +@Composable +fun ShortcutsList( + shortcuts: List, + onClick: (ShortcutOption) -> Unit +) { + Column { + Text( + text = stringResource(R.string.select_option), + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.SemiBold, + color = Colors.textPrimary, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + .padding(top = 28.dp) + ) + + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(Colors.backgroundSecondary) + .padding(top = 12.dp) + ) { + item { + Spacer(modifier = Modifier.height(12.dp)) + } + + items(shortcuts) { shortcut -> + ShortcutListItem( + shortcutOption = shortcut, + onClick = onClick + ) + } + } + } +} + +@Preview(showBackground = true) +@Composable +fun PreviewShortcutsList() { + val shortcuts = ShortcutOption.entries + + ShortcutsList( + shortcuts = shortcuts, + onClick = {} + ) +} \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutsPane.kt b/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutsPane.kt new file mode 100644 index 0000000000..27d1a5390e --- /dev/null +++ b/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutsPane.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2025 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 de.schildbach.wallet.ui.main.shortcuts + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import org.dash.wallet.common.ui.components.MyTheme.Colors + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun ShortcutsPane( + shortcuts: List, + onClick: (ShortcutOption) -> Unit, + onLongClick: (ShortcutOption, Int) -> Unit +) { + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(unbounded = true) + .clip(RoundedCornerShape(12.dp)) + .background(Colors.backgroundSecondary) + .padding(horizontal = 8.dp, vertical = 4.dp) + ) { + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.Top, + modifier = Modifier.fillMaxWidth() + ) { + val itemWidth = 1f / shortcuts.size + shortcuts.forEachIndexed { index, shortcut -> + Shortcut( + shortcutOption = shortcut, + modifier = Modifier + .weight(itemWidth) + .padding(6.dp) + .combinedClickable( + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(color = Colors.textPrimary, bounded = false, radius = 50.dp), + onClick = { onClick(shortcut) }, + onLongClick = { onLongClick(shortcut, index) }, + ) + ) + } + } + } +} diff --git a/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutsViewModel.kt b/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutsViewModel.kt new file mode 100644 index 0000000000..dfd035bb37 --- /dev/null +++ b/wallet/src/de/schildbach/wallet/ui/main/shortcuts/ShortcutsViewModel.kt @@ -0,0 +1,177 @@ +/* + * Copyright 2025 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 de.schildbach.wallet.ui.main.shortcuts + +import android.util.Log +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import de.schildbach.wallet.service.DeviceInfoProvider +import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import org.dash.wallet.common.Configuration +import org.dash.wallet.common.WalletDataProvider +import org.dash.wallet.common.data.WalletUIConfig +import org.dash.wallet.integrations.coinbase.repository.CoinBaseRepositoryInt +import org.dash.wallet.integrations.uphold.api.TopperClient +import javax.inject.Inject + +@HiltViewModel +class ShortcutsViewModel @Inject constructor( + private val walletUIConfig: WalletUIConfig, + private val config: Configuration, + private val walletData: WalletDataProvider, + private val shortcutProvider: ShortcutProvider, + private val deviceInfo: DeviceInfoProvider, + private val topperClient: TopperClient, + private val coinBaseRepository: CoinBaseRepositoryInt +): ViewModel() { + private val maxShortcuts = if (deviceInfo.isSmallScreen) 3 else 4 + private var isPassphraseVerified = true + private val hasCustomShortcuts: Boolean + get() = shortcutProvider.customShortcuts.value.isNotEmpty() + + private var _userHasBalance = true + var userHasBalance: Boolean + get() = _userHasBalance + set(value) { + _userHasBalance = value + + if (!hasCustomShortcuts) { + shortcuts = getPresetShortcuts().take(maxShortcuts) + } + } + + private var _userHasContacts = true + var userHasContacts: Boolean + get() = _userHasContacts + set(value) { + _userHasContacts = value + + if (!hasCustomShortcuts) { + shortcuts = getPresetShortcuts().take(maxShortcuts) + } + } + + var shortcuts by mutableStateOf(getPresetShortcuts().take(maxShortcuts)) + var showShortcutInfo by mutableStateOf(false) + + val isCoinbaseAuthenticated: Boolean + get() = coinBaseRepository.isAuthenticated + + init { + shortcutProvider.customShortcuts + .onEach { Log.i("SHORTCUTS", "size: ${it.size}") } + .filterNot { it.isEmpty() } + .onEach { shortcuts = it.take(maxShortcuts) } + .launchIn(viewModelScope) + + // Check if need to show the shortcut info panel + viewModelScope.launch { + val isHidden = walletUIConfig.get(WalletUIConfig.IS_SHORTCUT_INFO_HIDDEN) + showShortcutInfo = if (config.wasUpgraded()) { + // If upgraded, show immediately if not already hidden + isHidden != true + } else if (isHidden == null) { + // New install and the first time opening the app - don't show + // Update IS_SHORTCUT_INFO_HIDDEN to detect non-first launch next time + walletUIConfig.set(WalletUIConfig.IS_SHORTCUT_INFO_HIDDEN, false) + false + } else { + // New install, not the first time opening the app, show if not hidden + !isHidden + } + } + } + + fun getAllShortcutOptions(replacingShortcut: ShortcutOption): List { + return ShortcutOption.entries.filterNot { it == replacingShortcut || it == ShortcutOption.SECURE_NOW } + } + + fun refreshIsPassphraseVerified() { + isPassphraseVerified = !config.remindBackupSeed + + if (isPassphraseVerified && shortcuts.contains(ShortcutOption.SECURE_NOW)) { + if (hasCustomShortcuts) { + removeSecureNowShortcut() + } else { + shortcuts = getPresetShortcuts().take(maxShortcuts) + } + } + } + + fun replaceShortcut(oldIndex: Int, new: ShortcutOption) { + if (oldIndex !in shortcuts.indices) { + return + } + + val currentShortcuts = shortcuts.toMutableList() + + if (currentShortcuts[oldIndex] == ShortcutOption.SECURE_NOW) { + // Don't allow replacing SECURE_NOW shortcut + return + } + + currentShortcuts[oldIndex] = new + val shortcutIds = currentShortcuts.map { it.id } + .take(maxShortcuts) + .toIntArray() + viewModelScope.launch { + shortcutProvider.setCustomShortcuts(shortcutIds) + } + } + + fun hideShortcutInfo() { + viewModelScope.launch { + showShortcutInfo = false + walletUIConfig.set(WalletUIConfig.IS_SHORTCUT_INFO_HIDDEN, true) + } + } + + private fun removeSecureNowShortcut() { + val currentShortcuts = shortcuts.toMutableList() + val index = currentShortcuts.indexOf(ShortcutOption.SECURE_NOW) + currentShortcuts.removeAt(index) + val shortcutIds = currentShortcuts.map { it.id } + .take(maxShortcuts) + .toIntArray() + viewModelScope.launch { + shortcutProvider.setCustomShortcuts(shortcutIds) + } + } + + private fun getPresetShortcuts(): List { + return shortcutProvider.getFilteredShortcuts( + isPassphraseVerified = isPassphraseVerified, + userHasBalance = userHasBalance, + userHasContacts = userHasContacts + ) + } + + suspend fun getTopperUrl(walletName: String): String { + return topperClient.getOnRampUrl( + walletUIConfig.getExchangeCurrencyCode(), + walletData.freshReceiveAddress(), + walletName + ) + } +} \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/ui/widget/ShortcutButton.kt b/wallet/src/de/schildbach/wallet/ui/widget/ShortcutButton.kt deleted file mode 100644 index 222a06007c..0000000000 --- a/wallet/src/de/schildbach/wallet/ui/widget/ShortcutButton.kt +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2020 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 de.schildbach.wallet.ui.widget - -import android.content.Context -import android.content.res.Resources -import android.util.AttributeSet -import android.view.Gravity -import android.view.LayoutInflater -import android.view.View -import android.widget.LinearLayout -import androidx.appcompat.content.res.AppCompatResources -import androidx.core.content.res.ResourcesCompat -import com.tbuonomo.viewpagerdotsindicator.setPaddingVertical -import de.schildbach.wallet_test.R -import de.schildbach.wallet_test.databinding.ShortcutButtonBinding -import kotlin.math.roundToInt - -class ShortcutButton : LinearLayout { - - companion object { - val DEFAULT_MARGIN_PX = dpToPx(4) - - private fun dpToPx(dp: Int): Int { - val density = Resources.getSystem().displayMetrics.density - return (dp.toFloat() * density).roundToInt() - } - } - - private var marginsSet = false - var shouldAppear: Boolean = true - private val binding = ShortcutButtonBinding.inflate(LayoutInflater.from(context), this) - - init { - setBackgroundResource(R.drawable.white_button_background_no_shadow) - orientation = VERTICAL - gravity = Gravity.CENTER - setPaddingVertical(DEFAULT_MARGIN_PX) - } - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - val attrsArray = context.obtainStyledAttributes(attrs, R.styleable.ShortcutButton) - try { - val drawableResId = attrsArray.getResourceId(R.styleable.ShortcutButton_src, -1) - if (drawableResId > -1) { - val actionIconDrawable = AppCompatResources.getDrawable(context, drawableResId) - if (actionIconDrawable != null) { - binding.actionIcon.setImageDrawable(actionIconDrawable) - } - } else { - binding.actionIcon.visibility = View.GONE - } - val actionText = attrsArray.getString(R.styleable.ShortcutButton_text) - if (actionText != null) { - binding.actionText.text = actionText - } else { - binding.actionText.visibility = View.GONE - } - val backgroundResId = attrsArray.getResourceId( - R.styleable.ShortcutButton_background, - R.drawable.white_button_background_no_shadow - ) - setBackgroundResource(backgroundResId) - val customTextColor = attrsArray.getColorStateList(R.styleable.ShortcutButton_shortcut_text_color) - if (customTextColor != null) { - binding.actionText.setTextColor(customTextColor) - } - val actionActive = attrsArray.getBoolean(R.styleable.ShortcutButton_shortcut_active, true) - setActive(actionActive) - } finally { - attrsArray.recycle() - } - } - - constructor( - context: Context, - iconResId: Int = 0, - textResIt: Int = 0, - onClickListener: OnClickListener? = null, - backgroundResId: Int = 0, - textColorResId: Int = 0 - ) : super(context) { - if (iconResId != 0) { - binding.actionIcon.setImageResource(iconResId) - } else { - binding.actionIcon.visibility = View.GONE - } - if (textResIt != 0) { - binding.actionText.setText(textResIt) - } else { - binding.actionText.visibility = View.GONE - } - setOnClickListener(onClickListener) - if (backgroundResId != 0) { - setBackgroundResource(backgroundResId) - } - if (textColorResId != 0) { - binding.actionText.setTextColor(ResourcesCompat.getColor(resources, textColorResId, null)) - } - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - super.onMeasure(heightMeasureSpec, heightMeasureSpec) - if (!marginsSet) { - layoutParams = (layoutParams as MarginLayoutParams).run { - topMargin = DEFAULT_MARGIN_PX - bottomMargin = DEFAULT_MARGIN_PX - this - } - marginsSet = true - } - } - - private fun setActive(active: Boolean) { - if (active) { - binding.actionIcon.colorFilter = null - binding.actionIcon.alpha = 1.0f - alpha = 1.0f - } else { - val tintColor = ResourcesCompat.getColor(resources, R.color.dash_gray, null) - binding.actionIcon.setColorFilter(tintColor) - binding.actionIcon.alpha = 0.7f - alpha = 0.5f - } - } -} diff --git a/wallet/src/de/schildbach/wallet/ui/widget/ShortcutsPane.kt b/wallet/src/de/schildbach/wallet/ui/widget/ShortcutsPane.kt deleted file mode 100644 index b32e19075f..0000000000 --- a/wallet/src/de/schildbach/wallet/ui/widget/ShortcutsPane.kt +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 2020 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 de.schildbach.wallet.ui.widget - -import android.content.Context -import android.util.AttributeSet -import android.util.DisplayMetrics -import android.view.View -import android.view.ViewGroup -import android.view.ViewTreeObserver -import androidx.core.view.children -import com.google.android.flexbox.AlignItems -import com.google.android.flexbox.FlexDirection -import com.google.android.flexbox.FlexboxLayout -import com.google.android.flexbox.JustifyContent -import de.schildbach.wallet.Constants -import de.schildbach.wallet_test.R -import kotlin.math.min - -class ShortcutsPane(context: Context, attrs: AttributeSet) : FlexboxLayout(context, attrs), View.OnClickListener { - - val secureNowButton: ShortcutButton by lazy { - ShortcutButton( - context, - R.drawable.ic_shortcut_secure_now, - R.string.shortcut_secure_now, - this - ) - } - val receiveButton: ShortcutButton by lazy { - ShortcutButton( - context, - R.drawable.ic_shortcut_receive, - R.string.shortcut_receive, - this - ) - } - val scanToPayButton: ShortcutButton by lazy { - ShortcutButton( - context, - R.drawable.ic_shortcut_scan_to_pay, - R.string.shortcut_scan_to_pay, - this - ) - } - val payToAddressButton: ShortcutButton by lazy { - ShortcutButton( - context, - R.drawable.ic_shortcut_pay_to_address, - R.string.shortcut_pay_to_address, - this - ) - } - val payToContactButton: ShortcutButton by lazy { - ShortcutButton(context, - R.drawable.ic_shortcut_pay_to_contact, - R.string.shortcut_pay_to_contact, - this) - } - val buySellButton: ShortcutButton by lazy { - ShortcutButton( - context, - R.drawable.ic_shortcut_buy_sell_dash, - R.string.shortcut_buy_sell, - this - ) - } - val importPrivateKey: ShortcutButton by lazy { - ShortcutButton( - context, - R.drawable.ic_shortcut_import_key, - R.string.shortcut_import_key, - this - ) - } - val explore: ShortcutButton by lazy { - ShortcutButton( - context, - R.drawable.ic_shortcut_bar_explore, - R.string.menu_explore_title, - this - ) - } - - private var isSmallScreen = resources.displayMetrics.densityDpi <= DisplayMetrics.DENSITY_MEDIUM - private var onShortcutClickListener: OnClickListener? = null - - private val shortcuts = listOf( - secureNowButton, - explore, - receiveButton, - payToAddressButton, - // TODO: for the time being, never display the "Send to Contact" button - // payToContactButton, - buySellButton, - scanToPayButton - ) - - var isPassphraseVerified: Boolean = true - set(value) { - secureNowButton.shouldAppear = !value - - if (field != value) { - field = value - refresh() - } - } - - var userHasBalance: Boolean = true - set(value) { - scanToPayButton.shouldAppear = value - buySellButton.shouldAppear = !value - payToAddressButton.shouldAppear = value - // TODO: will the send to contact button ever be displayed, if not remove - // payToContactButton.shouldAppear = value && userHasContacts - - if (field != value) { - field = value - refresh() - } - } - - var userHasContacts: Boolean = false - set(value) { - // TODO: will the send to contact button ever be displayed, if not remove - // payToContactButton.shouldAppear = value && userHasBalance - - if (field != value) { - field = value - refresh() - } - } - - init { - setBackgroundResource(R.drawable.white_background_rounded) - minimumHeight = 180 - flexDirection = FlexDirection.ROW - justifyContent = JustifyContent.SPACE_EVENLY - alignItems = AlignItems.CENTER - if (isInEditMode) { - refresh() - } - val onPreDrawListener = object : ViewTreeObserver.OnPreDrawListener { - override fun onPreDraw(): Boolean { - viewTreeObserver.removeOnPreDrawListener(this) - val sizeRation = width.toFloat() / height.toFloat() - isSmallScreen = (sizeRation < 3.3) - refresh() - return false - } - } - viewTreeObserver.addOnPreDrawListener(onPreDrawListener) - } - - private fun refresh() { - var slotsLeft = if (isSmallScreen) 3 else 4 - - shortcuts.forEach { btn -> - if (btn.shouldAppear && slotsLeft > 0) { - addShortcut(btn) - slotsLeft-- - } else { - removeShortcut(btn) - } - } - } - - private fun addShortcut(shortcut: ShortcutButton) { - if (!children.contains(shortcut)) { - val index = min(childCount, shortcuts.indexOf(shortcut)) - val layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) - addView(shortcut, index, layoutParams) - } - } - - private fun removeShortcut(shortcut: ShortcutButton) { - if (children.contains(shortcut)) { - removeView(shortcut) - } - } - - fun setOnShortcutClickListener(listener: OnClickListener) { - onShortcutClickListener = listener - setOnClickListener(null) - } - - override fun onClick(v: View) { - onShortcutClickListener?.onClick(v) - } -} diff --git a/wallet/staging/res/values/values.xml b/wallet/staging/res/values/values.xml index 02a2df1458..dcc8c4b206 100644 --- a/wallet/staging/res/values/values.xml +++ b/wallet/staging/res/values/values.xml @@ -20,9 +20,11 @@ https://insight.testnet.networks.dash.org:3002/insight/ + https://blockchair.com/dash/ Insight + Blockchair \ No newline at end of file diff --git a/wallet/testNet3/res/drawable-xhdpi/currency_symbol_mdash.png b/wallet/testNet3/res/drawable-xhdpi/currency_symbol_mdash.png deleted file mode 100644 index 00f75445bb..0000000000 Binary files a/wallet/testNet3/res/drawable-xhdpi/currency_symbol_mdash.png and /dev/null differ diff --git a/wallet/testNet3/res/drawable-xhdpi/currency_symbol_udash.png b/wallet/testNet3/res/drawable-xhdpi/currency_symbol_udash.png deleted file mode 100644 index 1d368a72e3..0000000000 Binary files a/wallet/testNet3/res/drawable-xhdpi/currency_symbol_udash.png and /dev/null differ diff --git a/wallet/testNet3/res/values/values.xml b/wallet/testNet3/res/values/values.xml index 3f420cf634..5c6e0d5b4e 100644 --- a/wallet/testNet3/res/values/values.xml +++ b/wallet/testNet3/res/values/values.xml @@ -21,9 +21,11 @@ http://insight.testnet.networks.dash.org/insight/ + https://blockchair.com/dash/ Insight + Blockchair \ No newline at end of file