Add seed type toggle to onboarding screen

This commit is contained in:
pokkst 2023-12-05 11:13:20 -06:00
parent fc853dc55d
commit 5893c52c27
No known key found for this signature in database
GPG key ID: EC4FAAA66859FAA4
4 changed files with 125 additions and 34 deletions

View file

@ -94,6 +94,9 @@ public class OnboardingFragment extends Fragment implements NodeSelectionBottomS
private Button selectNodeButton; private Button selectNodeButton;
private SwitchCompat showXmrchanSwitch; private SwitchCompat showXmrchanSwitch;
private ImageView xmrchanOnboardingImage; private ImageView xmrchanOnboardingImage;
private TextView seedTypeLabelTextView;
private TextView seedTypeTextView;
private TextView seedTypeDescTextView;
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 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); advancedOptionsLayout = view.findViewById(R.id.more_options_layout);
showXmrchanSwitch = view.findViewById(R.id.show_xmrchan_switch); showXmrchanSwitch = view.findViewById(R.id.show_xmrchan_switch);
xmrchanOnboardingImage = view.findViewById(R.id.xmrchan_onboarding_imageview); 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(); bindListeners();
bindObservers(); bindObservers();
@ -141,6 +147,20 @@ public class OnboardingFragment extends Fragment implements NodeSelectionBottomS
mViewModel.enableCreateButton.observe(getViewLifecycleOwner(), enable -> { mViewModel.enableCreateButton.observe(getViewLifecycleOwner(), enable -> {
createWalletButton.setEnabled(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() { private void bindListeners() {
@ -198,16 +218,6 @@ public class OnboardingFragment extends Fragment implements NodeSelectionBottomS
@Override @Override
public void afterTextChanged(Editable editable) { public void afterTextChanged(Editable editable) {
String text = editable.toString(); 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()) { if (text.isEmpty()) {
createWalletButton.setText(R.string.create_wallet); 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) -> { torSwitch.setOnCheckedChangeListener((compoundButton, b) -> {
PrefService.getInstance().edit().putBoolean(Constants.PREF_USES_TOR, b).apply(); PrefService.getInstance().edit().putBoolean(Constants.PREF_USES_TOR, b).apply();
if (b) { 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() { private void prepareDefaultNode() {
PrefService.getInstance().getNode(); PrefService.getInstance().getNode();
} }

View file

@ -25,6 +25,8 @@ public class OnboardingViewModel extends ViewModel {
public LiveData<Boolean> showMoreOptions = _showMoreOptions; public LiveData<Boolean> showMoreOptions = _showMoreOptions;
private final MutableLiveData<Boolean> _enableCreateButton = new MutableLiveData<>(true); private final MutableLiveData<Boolean> _enableCreateButton = new MutableLiveData<>(true);
public LiveData<Boolean> enableCreateButton = _enableCreateButton; public LiveData<Boolean> enableCreateButton = _enableCreateButton;
private final MutableLiveData<SeedType> _seedType = new MutableLiveData<>(SeedType.POLYSEED);
public LiveData<SeedType> seedType = _seedType;
private String proxyAddress = ""; private String proxyAddress = "";
private String proxyPort = ""; 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) { public void setProxyAddress(String address) {
this.proxyAddress = 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) { public void createOrImportWallet(Activity mainActivity, String walletPassword, String confirmedPassword, String walletSeed, String restoreHeightText, boolean useOffset) {
MoneroApplication application = (MoneroApplication)mainActivity.getApplication(); MoneroApplication application = (MoneroApplication)mainActivity.getApplication();
application.getExecutor().execute(() -> { application.getExecutor().execute(() -> {
mainActivity.runOnUiThread(() -> { _enableCreateButton.postValue(false);
_enableCreateButton.setValue(false);
});
String offset = useOffset ? walletPassword : ""; String offset = useOffset ? walletPassword : "";
if (!walletPassword.isEmpty()) { if (!walletPassword.isEmpty()) {
if(!walletPassword.equals(confirmedPassword)) { 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()); mainActivity.runOnUiThread(() -> Toast.makeText(mainActivity, application.getString(R.string.invalid_confirmed_password), Toast.LENGTH_SHORT).show());
return; return;
} }
@ -85,19 +89,29 @@ public class OnboardingViewModel extends ViewModel {
} }
if (walletSeed.isEmpty()) { if (walletSeed.isEmpty()) {
if(offset.isEmpty()) { SeedType seedTypeValue = seedType.getValue();
mainActivity.runOnUiThread(() -> { if(seedTypeValue == null) return;
_enableCreateButton.setValue(true);
Toast.makeText(mainActivity, application.getString(R.string.invalid_empty_passphrase), Toast.LENGTH_SHORT).show(); if(seedTypeValue == SeedType.POLYSEED) {
}); if(offset.isEmpty()) {
return; mainActivity.runOnUiThread(() -> {
} else { _enableCreateButton.postValue(true);
wallet = WalletManager.getInstance().createWalletPolyseed(walletFile, walletPassword, offset, Constants.MNEMONIC_LANGUAGE); 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 { } else {
if (getMnemonicType(walletSeed) == SeedType.UNKNOWN) { if (getMnemonicType(walletSeed) == SeedType.UNKNOWN) {
mainActivity.runOnUiThread(() -> { mainActivity.runOnUiThread(() -> {
_enableCreateButton.setValue(true); _enableCreateButton.postValue(true);
Toast.makeText(mainActivity, application.getString(R.string.invalid_mnemonic_code), Toast.LENGTH_SHORT).show(); Toast.makeText(mainActivity, application.getString(R.string.invalid_mnemonic_code), Toast.LENGTH_SHORT).show();
}); });
return; return;
@ -110,14 +124,14 @@ public class OnboardingViewModel extends ViewModel {
Wallet.Status walletStatus = wallet.getStatus(); Wallet.Status walletStatus = wallet.getStatus();
wallet.close(); wallet.close();
boolean ok = walletStatus.isOk(); 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) { if (ok) {
((MainActivity)mainActivity).init(walletFile, walletPassword); ((MainActivity)mainActivity).init(walletFile, walletPassword);
mainActivity.runOnUiThread(mainActivity::onBackPressed); mainActivity.runOnUiThread(mainActivity::onBackPressed);
} else { } else {
mainActivity.runOnUiThread(() -> { mainActivity.runOnUiThread(() -> {
_enableCreateButton.setValue(true); _enableCreateButton.postValue(true);
Toast.makeText(mainActivity, application.getString(R.string.create_wallet_failed, walletStatus.getErrorString()), Toast.LENGTH_SHORT).show(); 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) { public SeedType getMnemonicType(String seed) {
String[] words = seed.split("\\s"); 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; return SeedType.POLYSEED;
} else if (words.length == 25){ } else if (words.length == 25 && seedTypeValue == SeedType.LEGACY){
return SeedType.LEGACY; return SeedType.LEGACY;
} else { } else {
return SeedType.UNKNOWN; return SeedType.UNKNOWN;
} }
} }
private Wallet createTempWallet(File tmpWalletFile) {
return WalletManager.getInstance().createWallet(tmpWalletFile, "", Constants.MNEMONIC_LANGUAGE, 0);
}
public enum SeedType { public enum SeedType {
LEGACY, LEGACY(R.string.seed_desc_legacy),
POLYSEED, POLYSEED(R.string.seed_desc_polyseed),
UNKNOWN UNKNOWN(0);
private int descResId;
SeedType(int descResId) {
this.descResId = descResId;
}
public int getDescResId() {
return descResId;
}
} }
} }

View file

@ -7,8 +7,9 @@
<ImageView <ImageView
android:id="@+id/xmrchan_onboarding_imageview" android:id="@+id/xmrchan_onboarding_imageview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="match_parent"
android:scaleType="fitEnd" android:scaleType="fitEnd"
android:adjustViewBounds="false"
android:src="@drawable/xmrchan_half" android:src="@drawable/xmrchan_half"
app:layout_constraintBottom_toBottomOf="parent"/> app:layout_constraintBottom_toBottomOf="parent"/>
<ScrollView <ScrollView
@ -67,11 +68,12 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:layout_marginTop="8dp" android:layout_marginTop="16dp"
android:text="@string/more_options" android:text="@string/more_options"
android:textStyle="bold" android:textStyle="bold"
android:paddingTop="4dp" android:paddingTop="4dp"
android:paddingBottom="4dp" android:paddingBottom="4dp"
android:textSize="18sp"
app:layout_constraintBottom_toTopOf="@id/more_options_layout" app:layout_constraintBottom_toTopOf="@id/more_options_layout"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
@ -132,10 +134,39 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/wallet_seed_edittext" app:layout_constraintBottom_toTopOf="@id/seed_type_label_textview"
app:layout_constraintTop_toBottomOf="@id/show_xmrchan_switch" app:layout_constraintTop_toBottomOf="@id/show_xmrchan_switch"
tools:ignore="SpeakableTextPresentCheck" /> tools:ignore="SpeakableTextPresentCheck" />
<TextView
android:id="@+id/seed_type_label_textview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Seed version"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/seed_type_name_textview"
app:layout_constraintTop_toBottomOf="@id/select_node_button"
app:layout_constraintBottom_toTopOf="@id/seed_type_name_textview"/>
<TextView
android:id="@+id/seed_type_name_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="POLYSEED"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/seed_type_label_textview"
app:layout_constraintBottom_toBottomOf="@id/seed_type_label_textview"/>
<TextView
android:id="@+id/seed_type_desc_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/oled_addressListColor"
android:paddingBottom="16dp"
android:paddingTop="8dp"
android:text="16 words instead of 25; just as secure."
app:layout_constraintTop_toBottomOf="@id/seed_type_label_textview"
app:layout_constraintBottom_toTopOf="@id/wallet_seed_edittext"/>
<EditText <EditText
android:id="@+id/wallet_seed_edittext" android:id="@+id/wallet_seed_edittext"
android:layout_width="0dp" android:layout_width="0dp"
@ -147,7 +178,7 @@
app:layout_constraintBottom_toTopOf="@id/wallet_restore_height_edittext" app:layout_constraintBottom_toTopOf="@id/wallet_restore_height_edittext"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/select_node_button" /> app:layout_constraintTop_toBottomOf="@id/seed_type_desc_textview" />
<EditText <EditText
android:id="@+id/wallet_restore_height_edittext" android:id="@+id/wallet_restore_height_edittext"

View file

@ -153,5 +153,7 @@
<string name="scan_qr_code_for_address_field">Scan QR code for address field</string> <string name="scan_qr_code_for_address_field">Scan QR code for address field</string>
<string name="copy_transaction_hash">Copy transaction hash</string> <string name="copy_transaction_hash">Copy transaction hash</string>
<string name="copy_transaction_addr">Copy transaction address</string> <string name="copy_transaction_addr">Copy transaction address</string>
<string name="seed_desc_polyseed">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.</string>
<string name="seed_desc_legacy">Older, 25 word seed; supported in all Monero wallets. In Mysu, seed passphrase is not enforced for these wallets.</string>
</resources> </resources>