diff --git a/app/build.gradle b/app/build.gradle index c334305..550d54d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -134,7 +134,7 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.preference:preference:1.2.0' - implementation 'com.google.android.material:material:1.6.0' + implementation 'com.google.android.material:material:1.6.1' implementation 'com.journeyapps:zxing-android-embedded:4.3.0' implementation "com.squareup.okhttp3:okhttp:4.9.3" diff --git a/app/src/main/java/net/mynero/wallet/fragment/dialog/SendBottomSheetDialog.java b/app/src/main/java/net/mynero/wallet/fragment/dialog/SendBottomSheetDialog.java index 4c7b0e6..1285d87 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/dialog/SendBottomSheetDialog.java +++ b/app/src/main/java/net/mynero/wallet/fragment/dialog/SendBottomSheetDialog.java @@ -10,6 +10,7 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; +import android.widget.RadioGroup; import android.widget.TextView; import android.widget.Toast; @@ -58,13 +59,16 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { private TextView feeTextView; private TextView addressTextView; private TextView amountTextView; + private TextView feeRadioGroupLabelTextView; private Button createButton; private Button sendButton; private Button sendMaxButton; private ImageButton pasteAddressImageButton; private ImageButton scanAddressImageButton; + private RadioGroup feeRadioGroup; public UriData uriData = null; + public PendingTransaction.Priority priority; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -85,6 +89,8 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { feeTextView = view.findViewById(R.id.fee_textview); addressTextView = view.findViewById(R.id.address_pending_textview); amountTextView = view.findViewById(R.id.amount_pending_textview); + feeRadioGroup = view.findViewById(R.id.tx_fee_radiogroup); + feeRadioGroupLabelTextView = view.findViewById(R.id.tx_fee_radiogroup_label_textview); if (uriData != null) { addressEditText.setText(uriData.getAddress()); @@ -93,6 +99,18 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { } } + feeRadioGroup.check(R.id.low_fee_radiobutton); + priority = PendingTransaction.Priority.Priority_Low; + feeRadioGroup.setOnCheckedChangeListener((radioGroup, i) -> { + if(i == R.id.low_fee_radiobutton) { + priority = PendingTransaction.Priority.Priority_Low; + } else if(i == R.id.med_fee_radiobutton) { + priority = PendingTransaction.Priority.Priority_Medium; + } else if(i == R.id.high_fee_radiobutton) { + priority = PendingTransaction.Priority.Priority_High; + } + }); + pasteAddressImageButton.setOnClickListener(view1 -> { Context ctx = getContext(); if (ctx != null) { @@ -228,6 +246,8 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { feeTextView.setVisibility(View.VISIBLE); addressTextView.setVisibility(View.VISIBLE); amountTextView.setVisibility(View.VISIBLE); + feeRadioGroup.setVisibility(View.GONE); + feeRadioGroupLabelTextView.setVisibility(View.GONE); } else { sendButton.setVisibility(View.GONE); addressEditText.setVisibility(View.VISIBLE); @@ -240,6 +260,8 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { feeTextView.setVisibility(View.GONE); addressTextView.setVisibility(View.GONE); amountTextView.setVisibility(View.GONE); + feeRadioGroup.setVisibility(View.VISIBLE); + feeRadioGroupLabelTextView.setVisibility(View.VISIBLE); } } diff --git a/app/src/main/java/net/mynero/wallet/util/LegacyStorageHelper.java b/app/src/main/java/net/mynero/wallet/util/LegacyStorageHelper.java deleted file mode 100644 index 42b5c95..0000000 --- a/app/src/main/java/net/mynero/wallet/util/LegacyStorageHelper.java +++ /dev/null @@ -1,168 +0,0 @@ -package net.mynero.wallet.util; - -import android.Manifest; -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Environment; -import android.preference.PreferenceManager; - -import net.mynero.wallet.BuildConfig; -import net.mynero.wallet.model.WalletManager; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.IOException; -import java.nio.channels.FileChannel; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import lombok.RequiredArgsConstructor; -import timber.log.Timber; - -@RequiredArgsConstructor -public class LegacyStorageHelper { - private static final Pattern WALLET_PATTERN = Pattern.compile("^(.+) \\(([0-9]+)\\).keys$"); - private static final String MIGRATED_KEY = "migrated_legacy_storage"; - final private File srcDir; - final private File dstDir; - - static public void migrateWallets(Context context) { - try { - if (isStorageMigrated(context)) return; - if (!hasReadPermission(context)) { - // can't migrate - don't remember this, as the user may turn on permissions later - return; - } - final File oldRoot = getWalletRoot(); - if (!oldRoot.exists()) { - // nothing to migrate, so don't try again - setStorageMigrated(context); - return; - } - final File newRoot = Helper.getWalletRoot(context); - (new LegacyStorageHelper(oldRoot, newRoot)).migrate(); - setStorageMigrated(context); // done it once - don't try again - } catch (IllegalStateException ex) { - Timber.d(ex); - // nothing we can do here - } - } - - private static boolean isExternalStorageWritable() { - String state = Environment.getExternalStorageState(); - return Environment.MEDIA_MOUNTED.equals(state); - } - - private static File getWalletRoot() { - if (!isExternalStorageWritable()) - throw new IllegalStateException(); - - // wallet folder for legacy (pre-Q) installations - final String FLAVOR_SUFFIX = - (BuildConfig.FLAVOR.startsWith("prod") ? "" : "." + BuildConfig.FLAVOR) - + (BuildConfig.DEBUG ? "-debug" : ""); - final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX; - - File dir = new File(Environment.getExternalStorageDirectory(), WALLET_DIR); - if (!dir.exists() || !dir.isDirectory()) - throw new IllegalStateException(); - return dir; - } - - private static boolean hasReadPermission(Context context) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { - return context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_DENIED; - } else { - return true; - } - } - - private static String getUniqueName(File root, String name) { - if (!(new File(root, name + ".keys")).exists()) // does not exist => it's ok to use - return name; - - File[] wallets = root.listFiles( - (dir, filename) -> { - Matcher m = WALLET_PATTERN.matcher(filename); - if (m.find()) - return m.group(1).equals(name); - else return false; - }); - if (wallets.length == 0) return name + " (1)"; - int maxIndex = 0; - for (File wallet : wallets) { - try { - final Matcher m = WALLET_PATTERN.matcher(wallet.getName()); - if (!m.find()) - throw new IllegalStateException("this must match as it did before"); - final int index = Integer.parseInt(m.group(2)); - if (index > maxIndex) maxIndex = index; - } catch (NumberFormatException ex) { - // this cannot happen & we can ignore it if it does - } - } - return name + " (" + (maxIndex + 1) + ")"; - } - - public static boolean isStorageMigrated(Context context) { - return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(MIGRATED_KEY, false); - } - - public static void setStorageMigrated(Context context) { - PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(MIGRATED_KEY, true).apply(); - } - - public void migrate() { - String addressPrefix = WalletManager.getInstance().addressPrefix(); - File[] wallets = srcDir.listFiles((dir, filename) -> filename.endsWith(".keys")); - if (wallets == null) return; - for (File wallet : wallets) { - final String walletName = wallet.getName().substring(0, wallet.getName().length() - ".keys".length()); - if (addressPrefix.indexOf(getAddress(walletName).charAt(0)) < 0) { - Timber.d("skipping %s", walletName); - continue; - } - try { - copy(walletName); - } catch (IOException ex) { // something failed - try to clean up - deleteDst(walletName); - } - } - } - - // return "@" by default so we don't need to deal with null stuff - private String getAddress(String walletName) { - File addressFile = new File(srcDir, walletName + ".address.txt"); - if (!addressFile.exists()) return "@"; - try (BufferedReader addressReader = new BufferedReader(new FileReader(addressFile))) { - return addressReader.readLine(); - } catch (IOException ex) { - Timber.d(ex.getLocalizedMessage()); - } - return "@"; - } - - private void copy(String walletName) throws IOException { - final String dstName = getUniqueName(dstDir, walletName); - copyFile(new File(srcDir, walletName), new File(dstDir, dstName)); - copyFile(new File(srcDir, walletName + ".keys"), new File(dstDir, dstName + ".keys")); - } - - private void deleteDst(String walletName) { - // do our best, but if it fails, it fails - (new File(dstDir, walletName)).delete(); - (new File(dstDir, walletName + ".keys")).delete(); - } - - private void copyFile(File src, File dst) throws IOException { - if (!src.exists()) return; - Timber.d("%s => %s", src.getAbsolutePath(), dst.getAbsolutePath()); - try (FileChannel inChannel = new FileInputStream(src).getChannel(); - FileChannel outChannel = new FileOutputStream(dst).getChannel()) { - inChannel.transferTo(0, inChannel.size(), outChannel); - } - } -} diff --git a/app/src/main/java/net/mynero/wallet/util/LocaleHelper.java b/app/src/main/java/net/mynero/wallet/util/LocaleHelper.java deleted file mode 100644 index 99c5889..0000000 --- a/app/src/main/java/net/mynero/wallet/util/LocaleHelper.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2018-2020 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.mynero.wallet.util; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.Configuration; -import android.preference.PreferenceManager; - -import net.mynero.wallet.R; - -import java.util.ArrayList; -import java.util.Locale; - -public class LocaleHelper { - private static Locale SYSTEM_DEFAULT_LOCALE = Locale.getDefault(); - - public static ArrayList getAvailableLocales(Context context) { - ArrayList locales = new ArrayList<>(); - // R.string.available_locales gets generated in build.gradle by enumerating values-* folders - String[] availableLocales = context.getString(R.string.available_locales).split(","); - - for (String localeName : availableLocales) { - locales.add(Locale.forLanguageTag(localeName)); - } - - return locales; - } - - public static String getDisplayName(Locale locale, boolean sentenceCase) { - String displayName = locale.getDisplayName(locale); - - if (sentenceCase) { - displayName = toSentenceCase(displayName, locale); - } - - return displayName; - } - - public static Context setPreferredLocale(Context context) { - return setLocale(context, getPreferredLanguageTag(context)); - } - - public static Context setAndSaveLocale(Context context, String langaugeTag) { - savePreferredLangaugeTag(context, langaugeTag); - return setLocale(context, langaugeTag); - } - - private static Context setLocale(Context context, String languageTag) { - Locale locale = (languageTag.isEmpty()) ? SYSTEM_DEFAULT_LOCALE : Locale.forLanguageTag(languageTag); - Locale.setDefault(locale); - - Configuration configuration = context.getResources().getConfiguration(); - configuration.setLocale(locale); - configuration.setLayoutDirection(locale); - - return context.createConfigurationContext(configuration); - } - - public static void updateSystemDefaultLocale(Locale locale) { - SYSTEM_DEFAULT_LOCALE = locale; - } - - private static String toSentenceCase(String str, Locale locale) { - if (str.isEmpty()) { - return str; - } - - int firstCodePointLen = str.offsetByCodePoints(0, 1); - return str.substring(0, firstCodePointLen).toUpperCase(locale) - + str.substring(firstCodePointLen); - } - - public static Locale getPreferredLocale(Context context) { - String languageTag = getPreferredLanguageTag(context); - return languageTag.isEmpty() ? SYSTEM_DEFAULT_LOCALE : Locale.forLanguageTag(languageTag); - } - - public static String getPreferredLanguageTag(Context context) { - return PreferenceManager.getDefaultSharedPreferences(context) - .getString("preferred_locale", ""); - // cannot access getString here as it's done BEFORE string locale is set - } - - @SuppressLint("ApplySharedPref") - private static void savePreferredLangaugeTag(Context context, String locale) { - PreferenceManager.getDefaultSharedPreferences(context).edit() - .putString(context.getString(R.string.preferred_locale), locale).commit(); - } -} diff --git a/app/src/main/res/layout/send_bottom_sheet_dialog.xml b/app/src/main/res/layout/send_bottom_sheet_dialog.xml index 719d4e0..521b884 100644 --- a/app/src/main/res/layout/send_bottom_sheet_dialog.xml +++ b/app/src/main/res/layout/send_bottom_sheet_dialog.xml @@ -17,7 +17,7 @@ android:id="@+id/send_monero_textview" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="32dp" + android:layout_marginBottom="16dp" android:text="@string/send_monero" android:textSize="32sp" android:textStyle="bold" @@ -30,7 +30,7 @@ android:id="@+id/address_edittext" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginBottom="32dp" + android:layout_marginBottom="16dp" android:background="@drawable/edittext_bg" android:ellipsize="middle" android:hint="@string/address" @@ -39,7 +39,7 @@ app:layout_constraintEnd_toStartOf="@id/paste_address_imagebutton" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/send_monero_textview" - tools:visibility="gone" /> + tools:visibility="visible" /> + tools:visibility="visible" /> + tools:visibility="visible" /> + tools:visibility="visible" /> + tools:visibility="visible" />