From 5893c52c27685a5e22bd2e9577f2418f5628b8a5 Mon Sep 17 00:00:00 2001 From: pokkst Date: Tue, 5 Dec 2023 11:13:20 -0600 Subject: [PATCH] Add seed type toggle to onboarding screen --- .../onboarding/OnboardingFragment.java | 48 ++++++++++--- .../onboarding/OnboardingViewModel.java | 70 +++++++++++++------ .../main/res/layout/fragment_onboarding.xml | 39 +++++++++-- app/src/main/res/values/strings.xml | 2 + 4 files changed, 125 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingFragment.java b/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingFragment.java index f0d775a..fe6d901 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingFragment.java +++ b/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingFragment.java @@ -94,6 +94,9 @@ public class OnboardingFragment extends Fragment implements NodeSelectionBottomS private Button selectNodeButton; private SwitchCompat showXmrchanSwitch; private ImageView xmrchanOnboardingImage; + private TextView seedTypeLabelTextView; + private TextView seedTypeTextView; + private TextView seedTypeDescTextView; @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @@ -122,6 +125,9 @@ public class OnboardingFragment extends Fragment implements NodeSelectionBottomS advancedOptionsLayout = view.findViewById(R.id.more_options_layout); showXmrchanSwitch = view.findViewById(R.id.show_xmrchan_switch); xmrchanOnboardingImage = view.findViewById(R.id.xmrchan_onboarding_imageview); + seedTypeLabelTextView = view.findViewById(R.id.seed_type_label_textview); + seedTypeTextView = view.findViewById(R.id.seed_type_name_textview); + seedTypeDescTextView = view.findViewById(R.id.seed_type_desc_textview); bindListeners(); bindObservers(); @@ -141,6 +147,20 @@ public class OnboardingFragment extends Fragment implements NodeSelectionBottomS mViewModel.enableCreateButton.observe(getViewLifecycleOwner(), enable -> { createWalletButton.setEnabled(enable); }); + + mViewModel.seedType.observe(getViewLifecycleOwner(), seedType -> { + seedTypeTextView.setText(seedType.toString()); + seedTypeDescTextView.setText(getText(seedType.getDescResId())); + if(seedType == OnboardingViewModel.SeedType.LEGACY) { + seedOffsetCheckbox.setVisibility(View.VISIBLE); + walletRestoreHeightEditText.setVisibility(View.VISIBLE); + walletPasswordEditText.setHint(getString(R.string.password_optional)); + } else { + seedOffsetCheckbox.setVisibility(View.GONE); + walletRestoreHeightEditText.setVisibility(View.GONE); + walletPasswordEditText.setHint(getString(R.string.password_non_optional)); + } + }); } private void bindListeners() { @@ -198,16 +218,6 @@ public class OnboardingFragment extends Fragment implements NodeSelectionBottomS @Override public void afterTextChanged(Editable editable) { String text = editable.toString(); - OnboardingViewModel.SeedType seedType = mViewModel.getMnemonicType(text); - if(seedType == OnboardingViewModel.SeedType.LEGACY) { - seedOffsetCheckbox.setVisibility(View.VISIBLE); - walletRestoreHeightEditText.setVisibility(View.VISIBLE); - walletPasswordEditText.setHint(getString(R.string.password_optional)); - } else { - seedOffsetCheckbox.setVisibility(View.GONE); - walletRestoreHeightEditText.setVisibility(View.GONE); - walletPasswordEditText.setHint(getString(R.string.password_non_optional)); - } if (text.isEmpty()) { createWalletButton.setText(R.string.create_wallet); @@ -216,6 +226,11 @@ public class OnboardingFragment extends Fragment implements NodeSelectionBottomS } } }); + + seedTypeLabelTextView.setOnClickListener(v -> toggleSeedType()); + seedTypeTextView.setOnClickListener(v -> toggleSeedType()); + seedTypeDescTextView.setOnClickListener(v -> toggleSeedType()); + torSwitch.setOnCheckedChangeListener((compoundButton, b) -> { PrefService.getInstance().edit().putBoolean(Constants.PREF_USES_TOR, b).apply(); if (b) { @@ -257,6 +272,19 @@ public class OnboardingFragment extends Fragment implements NodeSelectionBottomS }); } + private void toggleSeedType() { + OnboardingViewModel.SeedType seedType = mViewModel.seedType.getValue(); + if(seedType == null) return; + OnboardingViewModel.SeedType newSeedType = OnboardingViewModel.SeedType.UNKNOWN; + if(seedType == OnboardingViewModel.SeedType.POLYSEED) { + newSeedType = OnboardingViewModel.SeedType.LEGACY; + } else if(seedType == OnboardingViewModel.SeedType.LEGACY) { + newSeedType = OnboardingViewModel.SeedType.POLYSEED; + } + + mViewModel.setSeedType(newSeedType); + } + private void prepareDefaultNode() { PrefService.getInstance().getNode(); } diff --git a/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingViewModel.java b/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingViewModel.java index 2dcc5d5..be6f74f 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingViewModel.java +++ b/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingViewModel.java @@ -25,6 +25,8 @@ public class OnboardingViewModel extends ViewModel { public LiveData showMoreOptions = _showMoreOptions; private final MutableLiveData _enableCreateButton = new MutableLiveData<>(true); public LiveData enableCreateButton = _enableCreateButton; + private final MutableLiveData _seedType = new MutableLiveData<>(SeedType.POLYSEED); + public LiveData seedType = _seedType; private String proxyAddress = ""; private String proxyPort = ""; @@ -53,6 +55,10 @@ public class OnboardingViewModel extends ViewModel { }); } + public void setSeedType(SeedType seedType) { + this._seedType.setValue(seedType); + } + public void setProxyAddress(String address) { this.proxyAddress = address; @@ -65,13 +71,11 @@ public class OnboardingViewModel extends ViewModel { public void createOrImportWallet(Activity mainActivity, String walletPassword, String confirmedPassword, String walletSeed, String restoreHeightText, boolean useOffset) { MoneroApplication application = (MoneroApplication)mainActivity.getApplication(); application.getExecutor().execute(() -> { - mainActivity.runOnUiThread(() -> { - _enableCreateButton.setValue(false); - }); + _enableCreateButton.postValue(false); String offset = useOffset ? walletPassword : ""; if (!walletPassword.isEmpty()) { if(!walletPassword.equals(confirmedPassword)) { - _enableCreateButton.setValue(true); + _enableCreateButton.postValue(true); mainActivity.runOnUiThread(() -> Toast.makeText(mainActivity, application.getString(R.string.invalid_confirmed_password), Toast.LENGTH_SHORT).show()); return; } @@ -85,19 +89,29 @@ public class OnboardingViewModel extends ViewModel { } if (walletSeed.isEmpty()) { - if(offset.isEmpty()) { - mainActivity.runOnUiThread(() -> { - _enableCreateButton.setValue(true); - Toast.makeText(mainActivity, application.getString(R.string.invalid_empty_passphrase), Toast.LENGTH_SHORT).show(); - }); - return; - } else { - wallet = WalletManager.getInstance().createWalletPolyseed(walletFile, walletPassword, offset, Constants.MNEMONIC_LANGUAGE); + SeedType seedTypeValue = seedType.getValue(); + if(seedTypeValue == null) return; + + if(seedTypeValue == SeedType.POLYSEED) { + if(offset.isEmpty()) { + mainActivity.runOnUiThread(() -> { + _enableCreateButton.postValue(true); + Toast.makeText(mainActivity, application.getString(R.string.invalid_empty_passphrase), Toast.LENGTH_SHORT).show(); + }); + return; + } else { + wallet = WalletManager.getInstance().createWalletPolyseed(walletFile, walletPassword, offset, Constants.MNEMONIC_LANGUAGE); + } + } else if(seedTypeValue == SeedType.LEGACY) { + File tmpWalletFile = new File(mainActivity.getApplicationInfo().dataDir, Constants.WALLET_NAME + "_tmp"); + Wallet tmpWallet = createTempWallet(tmpWalletFile); //we do this to get seed, then recover wallet so we can use seed offset + wallet = WalletManager.getInstance().recoveryWallet(walletFile, walletPassword, tmpWallet.getSeed(""), offset, restoreHeight); + tmpWalletFile.delete(); } } else { if (getMnemonicType(walletSeed) == SeedType.UNKNOWN) { mainActivity.runOnUiThread(() -> { - _enableCreateButton.setValue(true); + _enableCreateButton.postValue(true); Toast.makeText(mainActivity, application.getString(R.string.invalid_mnemonic_code), Toast.LENGTH_SHORT).show(); }); return; @@ -110,14 +124,14 @@ public class OnboardingViewModel extends ViewModel { Wallet.Status walletStatus = wallet.getStatus(); wallet.close(); boolean ok = walletStatus.isOk(); - //walletFile.delete(); // cache is broken for some reason when recovering wallets. delete the file here. this happens in monerujo too. + walletFile.delete(); // cache is broken for some reason when recovering wallets. delete the file here. this happens in monerujo too. if (ok) { ((MainActivity)mainActivity).init(walletFile, walletPassword); mainActivity.runOnUiThread(mainActivity::onBackPressed); } else { mainActivity.runOnUiThread(() -> { - _enableCreateButton.setValue(true); + _enableCreateButton.postValue(true); Toast.makeText(mainActivity, application.getString(R.string.create_wallet_failed, walletStatus.getErrorString()), Toast.LENGTH_SHORT).show(); }); } @@ -132,18 +146,34 @@ public class OnboardingViewModel extends ViewModel { public SeedType getMnemonicType(String seed) { String[] words = seed.split("\\s"); - if(words.length == 16) { + SeedType seedTypeValue = seedType.getValue(); + if(seedTypeValue == null) return SeedType.LEGACY; + if(words.length == 16 && seedTypeValue == SeedType.POLYSEED) { return SeedType.POLYSEED; - } else if (words.length == 25){ + } else if (words.length == 25 && seedTypeValue == SeedType.LEGACY){ return SeedType.LEGACY; } else { return SeedType.UNKNOWN; } } + private Wallet createTempWallet(File tmpWalletFile) { + return WalletManager.getInstance().createWallet(tmpWalletFile, "", Constants.MNEMONIC_LANGUAGE, 0); + } + public enum SeedType { - LEGACY, - POLYSEED, - UNKNOWN + LEGACY(R.string.seed_desc_legacy), + POLYSEED(R.string.seed_desc_polyseed), + UNKNOWN(0); + + private int descResId; + + SeedType(int descResId) { + this.descResId = descResId; + } + + public int getDescResId() { + return descResId; + } } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_onboarding.xml b/app/src/main/res/layout/fragment_onboarding.xml index 871d13d..47a5181 100644 --- a/app/src/main/res/layout/fragment_onboarding.xml +++ b/app/src/main/res/layout/fragment_onboarding.xml @@ -7,8 +7,9 @@ + + + + + app:layout_constraintTop_toBottomOf="@id/seed_type_desc_textview" /> Scan QR code for address field Copy transaction hash Copy transaction address + 16 words instead of 25; just as secure, but not supported in as many wallets right now. In Mysu, seed passphrase is enforced for these wallets. + Older, 25 word seed; supported in all Monero wallets. In Mysu, seed passphrase is not enforced for these wallets.