mirror of
https://codeberg.org/anoncontributorxmr/mysu.git
synced 2024-11-29 10:43:15 +00:00
Basic onboarding flow, allows for wallet passwords.
NOTE: This commit still logs seeds to files for dev purposes, as there is no UI for it yet.
This commit is contained in:
parent
6674f894b7
commit
528bc7c7c5
16 changed files with 384 additions and 297 deletions
|
@ -1,20 +1,30 @@
|
||||||
package com.m2049r.xmrwallet;
|
package com.m2049r.xmrwallet;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
|
|
||||||
|
import com.m2049r.xmrwallet.fragment.dialog.PasswordBottomSheetDialog;
|
||||||
|
import com.m2049r.xmrwallet.fragment.dialog.SendBottomSheetDialog;
|
||||||
|
import com.m2049r.xmrwallet.livedata.SingleLiveEvent;
|
||||||
import com.m2049r.xmrwallet.model.Wallet;
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
import com.m2049r.xmrwallet.model.WalletManager;
|
import com.m2049r.xmrwallet.model.WalletManager;
|
||||||
import com.m2049r.xmrwallet.service.AddressService;
|
import com.m2049r.xmrwallet.service.AddressService;
|
||||||
import com.m2049r.xmrwallet.service.BalanceService;
|
import com.m2049r.xmrwallet.service.BalanceService;
|
||||||
import com.m2049r.xmrwallet.service.HistoryService;
|
import com.m2049r.xmrwallet.service.HistoryService;
|
||||||
import com.m2049r.xmrwallet.service.MoneroHandlerThread;
|
import com.m2049r.xmrwallet.service.MoneroHandlerThread;
|
||||||
|
import com.m2049r.xmrwallet.service.PrefService;
|
||||||
import com.m2049r.xmrwallet.service.TxService;
|
import com.m2049r.xmrwallet.service.TxService;
|
||||||
|
import com.m2049r.xmrwallet.util.Constants;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity implements MoneroHandlerThread.Listener {
|
public class MainActivity extends AppCompatActivity implements MoneroHandlerThread.Listener, PasswordBottomSheetDialog.PasswordListener {
|
||||||
|
public final SingleLiveEvent restartEvents = new SingleLiveEvent();
|
||||||
private MoneroHandlerThread thread = null;
|
private MoneroHandlerThread thread = null;
|
||||||
private TxService txService = null;
|
private TxService txService = null;
|
||||||
private BalanceService balanceService = null;
|
private BalanceService balanceService = null;
|
||||||
|
@ -25,21 +35,39 @@ public class MainActivity extends AppCompatActivity implements MoneroHandlerThre
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
init();
|
File walletFile = new File(getApplicationInfo().dataDir, Constants.WALLET_NAME);
|
||||||
|
new PrefService(this);
|
||||||
|
|
||||||
|
if(walletFile.exists()) {
|
||||||
|
boolean promptPassword = PrefService.getInstance().getBoolean(Constants.PREF_USES_PASSWORD, false);
|
||||||
|
if(!promptPassword) {
|
||||||
|
init(walletFile, "");
|
||||||
|
} else {
|
||||||
|
PasswordBottomSheetDialog passwordDialog = new PasswordBottomSheetDialog();
|
||||||
|
passwordDialog.listener = this;
|
||||||
|
passwordDialog.show(getSupportFragmentManager(), null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navigate(R.id.onboarding_fragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void navigate(int destination) {
|
||||||
|
FragmentActivity activity = this;
|
||||||
|
FragmentManager fm = activity.getSupportFragmentManager();
|
||||||
|
NavHostFragment navHostFragment =
|
||||||
|
(NavHostFragment) fm.findFragmentById(R.id.nav_host_fragment);
|
||||||
|
if (navHostFragment != null) {
|
||||||
|
navHostFragment.getNavController().navigate(destination);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoneroHandlerThread getThread() {
|
public MoneroHandlerThread getThread() {
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
public void init(File walletFile, String password) {
|
||||||
File walletFile = new File(getApplicationInfo().dataDir, "xmr_wallet");
|
Wallet wallet = WalletManager.getInstance().openWallet(walletFile.getAbsolutePath(), password);
|
||||||
Wallet wallet = null;
|
|
||||||
if (walletFile.exists()) {
|
|
||||||
wallet = WalletManager.getInstance().openWallet(walletFile.getAbsolutePath(), "");
|
|
||||||
} else {
|
|
||||||
wallet = WalletManager.getInstance().createWallet(walletFile, "", "English", 0);
|
|
||||||
}
|
|
||||||
WalletManager.getInstance().setProxy("127.0.0.1:9050");
|
WalletManager.getInstance().setProxy("127.0.0.1:9050");
|
||||||
thread = new MoneroHandlerThread("WalletService", wallet, this);
|
thread = new MoneroHandlerThread("WalletService", wallet, this);
|
||||||
this.txService = new TxService(this, thread);
|
this.txService = new TxService(this, thread);
|
||||||
|
@ -55,4 +83,16 @@ public class MainActivity extends AppCompatActivity implements MoneroHandlerThre
|
||||||
this.balanceService.refreshBalance();
|
this.balanceService.refreshBalance();
|
||||||
this.addressService.refreshAddress();
|
this.addressService.refreshAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPasswordSuccess(String password) {
|
||||||
|
File walletFile = new File(getApplicationInfo().dataDir, Constants.WALLET_NAME);
|
||||||
|
init(walletFile, password);
|
||||||
|
restartEvents.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPasswordFail() {
|
||||||
|
Toast.makeText(this, R.string.bad_password, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package com.m2049r.xmrwallet.fragment.dialog;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||||
|
import com.m2049r.xmrwallet.R;
|
||||||
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
|
import com.m2049r.xmrwallet.model.WalletManager;
|
||||||
|
import com.m2049r.xmrwallet.service.BalanceService;
|
||||||
|
import com.m2049r.xmrwallet.service.TxService;
|
||||||
|
import com.m2049r.xmrwallet.util.Constants;
|
||||||
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class PasswordBottomSheetDialog extends BottomSheetDialogFragment {
|
||||||
|
public PasswordListener listener = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.password_bottom_sheet_dialog, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
File walletFile = new File(getActivity().getApplicationInfo().dataDir, Constants.WALLET_NAME);
|
||||||
|
|
||||||
|
ImageButton pastePasswordImageButton = view.findViewById(R.id.paste_password_imagebutton);
|
||||||
|
EditText passwordEditText = view.findViewById(R.id.wallet_password_edittext);
|
||||||
|
Button unlockWalletButton = view.findViewById(R.id.unlock_wallet_button);
|
||||||
|
|
||||||
|
pastePasswordImageButton.setOnClickListener(view1 -> {
|
||||||
|
passwordEditText.setText(Helper.getClipBoardText(view.getContext()));
|
||||||
|
});
|
||||||
|
|
||||||
|
unlockWalletButton.setOnClickListener(view1 -> {
|
||||||
|
String password = passwordEditText.getText().toString();
|
||||||
|
boolean success = checkPassword(walletFile, password);
|
||||||
|
if(success) {
|
||||||
|
listener.onPasswordSuccess(password);
|
||||||
|
dismiss();
|
||||||
|
} else {
|
||||||
|
listener.onPasswordFail();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkPassword(File walletFile, String password) {
|
||||||
|
Wallet wallet = WalletManager.getInstance().openWallet(walletFile.getAbsolutePath(), password);
|
||||||
|
boolean ok = wallet.getStatus().isOk();
|
||||||
|
wallet.close();
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface PasswordListener {
|
||||||
|
void onPasswordSuccess(String password);
|
||||||
|
void onPasswordFail();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
package com.m2049r.xmrwallet.fragment.dialog;
|
package com.m2049r.xmrwallet.fragment.dialog;
|
||||||
|
|
||||||
import android.content.ClipboardManager;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||||
import com.m2049r.xmrwallet.R;
|
import com.m2049r.xmrwallet.R;
|
||||||
import com.m2049r.xmrwallet.model.Wallet;
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
import com.m2049r.xmrwallet.service.BalanceService;
|
import com.m2049r.xmrwallet.service.BalanceService;
|
||||||
import com.m2049r.xmrwallet.service.TxService;
|
import com.m2049r.xmrwallet.service.TxService;
|
||||||
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -21,7 +21,6 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.lifecycle.Observer;
|
|
||||||
|
|
||||||
public class SendBottomSheetDialog extends BottomSheetDialogFragment {
|
public class SendBottomSheetDialog extends BottomSheetDialogFragment {
|
||||||
private MutableLiveData<Boolean> _sendingMax = new MutableLiveData<>(false);
|
private MutableLiveData<Boolean> _sendingMax = new MutableLiveData<>(false);
|
||||||
|
@ -42,12 +41,8 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment {
|
||||||
Button sendButton = view.findViewById(R.id.send_button);
|
Button sendButton = view.findViewById(R.id.send_button);
|
||||||
TextView sendAllTextView = view.findViewById(R.id.sending_all_textview);
|
TextView sendAllTextView = view.findViewById(R.id.sending_all_textview);
|
||||||
|
|
||||||
TxService.getInstance().clearSendEvent.observe(getViewLifecycleOwner(), o -> {
|
|
||||||
dismiss();
|
|
||||||
});
|
|
||||||
|
|
||||||
pasteAddressImageButton.setOnClickListener(view1 -> {
|
pasteAddressImageButton.setOnClickListener(view1 -> {
|
||||||
|
addressEditText.setText(Helper.getClipBoardText(view.getContext()));
|
||||||
});
|
});
|
||||||
|
|
||||||
sendMaxButton.setOnClickListener(view1 -> {
|
sendMaxButton.setOnClickListener(view1 -> {
|
||||||
|
@ -68,7 +63,12 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sendButton.setEnabled(false);
|
sendButton.setEnabled(false);
|
||||||
TxService.getInstance().sendTx(address, amount, sendAll);
|
boolean success = TxService.getInstance().sendTx(address, amount, sendAll);
|
||||||
|
if(success) {
|
||||||
|
dismiss();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.error_sending_tx), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
} else if (!validAddress) {
|
} else if (!validAddress) {
|
||||||
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
|
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
@ -32,7 +33,9 @@ import com.m2049r.xmrwallet.model.Wallet;
|
||||||
import com.m2049r.xmrwallet.service.AddressService;
|
import com.m2049r.xmrwallet.service.AddressService;
|
||||||
import com.m2049r.xmrwallet.service.BalanceService;
|
import com.m2049r.xmrwallet.service.BalanceService;
|
||||||
import com.m2049r.xmrwallet.service.HistoryService;
|
import com.m2049r.xmrwallet.service.HistoryService;
|
||||||
|
import com.m2049r.xmrwallet.service.PrefService;
|
||||||
import com.m2049r.xmrwallet.service.TxService;
|
import com.m2049r.xmrwallet.service.TxService;
|
||||||
|
import com.m2049r.xmrwallet.util.Constants;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
|
@ -49,9 +52,19 @@ public class HomeFragment extends Fragment implements TransactionInfoAdapter.TxI
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
MainActivity mainActivity = (MainActivity)getActivity();
|
||||||
mViewModel = new ViewModelProvider(this).get(HomeViewModel.class);
|
mViewModel = new ViewModelProvider(this).get(HomeViewModel.class);
|
||||||
|
|
||||||
|
boolean usesPassword = PrefService.getInstance().getBoolean(Constants.PREF_USES_PASSWORD, false);
|
||||||
|
if(!usesPassword) {
|
||||||
bindObservers(view);
|
bindObservers(view);
|
||||||
bindListeners(view);
|
bindListeners(view);
|
||||||
|
} else {
|
||||||
|
mainActivity.restartEvents.observe(getViewLifecycleOwner(), o -> {
|
||||||
|
bindObservers(view);
|
||||||
|
bindListeners(view);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindListeners(View view) {
|
private void bindListeners(View view) {
|
||||||
|
@ -79,11 +92,15 @@ public class HomeFragment extends Fragment implements TransactionInfoAdapter.TxI
|
||||||
TextView unlockedBalanceTextView = view.findViewById(R.id.balance_unlocked_textview);
|
TextView unlockedBalanceTextView = view.findViewById(R.id.balance_unlocked_textview);
|
||||||
TextView lockedBalanceTextView = view.findViewById(R.id.balance_locked_textview);
|
TextView lockedBalanceTextView = view.findViewById(R.id.balance_locked_textview);
|
||||||
|
|
||||||
BalanceService.getInstance().balance.observe(getViewLifecycleOwner(), balance -> {
|
BalanceService balanceService = BalanceService.getInstance();
|
||||||
|
HistoryService historyService = HistoryService.getInstance();
|
||||||
|
|
||||||
|
if(balanceService != null) {
|
||||||
|
balanceService.balance.observe(getViewLifecycleOwner(), balance -> {
|
||||||
unlockedBalanceTextView.setText(getString(R.string.wallet_balance_text, Wallet.getDisplayAmount(balance)));
|
unlockedBalanceTextView.setText(getString(R.string.wallet_balance_text, Wallet.getDisplayAmount(balance)));
|
||||||
});
|
});
|
||||||
|
|
||||||
BalanceService.getInstance().lockedBalance.observe(getViewLifecycleOwner(), lockedBalance -> {
|
balanceService.lockedBalance.observe(getViewLifecycleOwner(), lockedBalance -> {
|
||||||
if (lockedBalance == 0) {
|
if (lockedBalance == 0) {
|
||||||
lockedBalanceTextView.setVisibility(View.INVISIBLE);
|
lockedBalanceTextView.setVisibility(View.INVISIBLE);
|
||||||
} else {
|
} else {
|
||||||
|
@ -91,11 +108,13 @@ public class HomeFragment extends Fragment implements TransactionInfoAdapter.TxI
|
||||||
lockedBalanceTextView.setVisibility(View.VISIBLE);
|
lockedBalanceTextView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
TransactionInfoAdapter adapter = new TransactionInfoAdapter(this);
|
TransactionInfoAdapter adapter = new TransactionInfoAdapter(this);
|
||||||
txHistoryRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
txHistoryRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
txHistoryRecyclerView.setAdapter(adapter);
|
txHistoryRecyclerView.setAdapter(adapter);
|
||||||
HistoryService.getInstance().history.observe(getViewLifecycleOwner(), history -> {
|
if(historyService != null) {
|
||||||
|
historyService.history.observe(getViewLifecycleOwner(), history -> {
|
||||||
if (history.isEmpty()) {
|
if (history.isEmpty()) {
|
||||||
txHistoryRecyclerView.setVisibility(View.GONE);
|
txHistoryRecyclerView.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
|
@ -105,6 +124,7 @@ public class HomeFragment extends Fragment implements TransactionInfoAdapter.TxI
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClickTransaction(TransactionInfo txInfo) {
|
public void onClickTransaction(TransactionInfo txInfo) {
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
package com.m2049r.xmrwallet.fragment.onboarding;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import com.m2049r.xmrwallet.MainActivity;
|
||||||
|
import com.m2049r.xmrwallet.R;
|
||||||
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
|
import com.m2049r.xmrwallet.model.WalletManager;
|
||||||
|
import com.m2049r.xmrwallet.service.PrefService;
|
||||||
|
import com.m2049r.xmrwallet.util.Constants;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class OnboardingFragment extends Fragment {
|
||||||
|
|
||||||
|
private OnboardingViewModel mViewModel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.fragment_settings, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
mViewModel = new ViewModelProvider(this).get(OnboardingViewModel.class);
|
||||||
|
EditText walletPasswordEditText = view.findViewById(R.id.wallet_password_edittext);
|
||||||
|
EditText walletSeedEditText = view.findViewById(R.id.wallet_seed_edittext);
|
||||||
|
Button createWalletButton = view.findViewById(R.id.create_wallet_button);
|
||||||
|
createWalletButton.setOnClickListener(view1 -> {
|
||||||
|
String walletPassword = walletPasswordEditText.getText().toString();
|
||||||
|
if(!walletPassword.isEmpty()) {
|
||||||
|
PrefService.getInstance().edit().putBoolean(Constants.PREF_USES_PASSWORD, true).apply();
|
||||||
|
}
|
||||||
|
String walletSeed = walletSeedEditText.getText().toString().trim();
|
||||||
|
File walletFile = new File(getActivity().getApplicationInfo().dataDir, Constants.WALLET_NAME);
|
||||||
|
Wallet wallet = null;
|
||||||
|
if(walletSeed.isEmpty()) {
|
||||||
|
wallet = WalletManager.getInstance().createWallet(walletFile, walletPassword, Constants.MNEMONIC_LANGUAGE, 0);
|
||||||
|
} else {
|
||||||
|
wallet = WalletManager.getInstance().recoveryWallet(walletFile, walletPassword, walletSeed, "", 0);
|
||||||
|
}
|
||||||
|
wallet.close();
|
||||||
|
((MainActivity)getActivity()).init(walletFile, walletPassword);
|
||||||
|
getActivity().onBackPressed();
|
||||||
|
});
|
||||||
|
walletSeedEditText.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable editable) {
|
||||||
|
String text = editable.toString();
|
||||||
|
if(text.isEmpty()) {
|
||||||
|
createWalletButton.setText(R.string.create_wallet);
|
||||||
|
} else {
|
||||||
|
createWalletButton.setText(R.string.menu_restore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.m2049r.xmrwallet.fragment.onboarding;
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
public class OnboardingViewModel extends ViewModel {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.m2049r.xmrwallet.service;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import com.m2049r.xmrwallet.MainActivity;
|
||||||
|
|
||||||
|
public class PrefService extends ServiceBase {
|
||||||
|
public static SharedPreferences instance = null;
|
||||||
|
|
||||||
|
public static SharedPreferences getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrefService(MainActivity mainActivity) {
|
||||||
|
super(mainActivity, null);
|
||||||
|
instance = mainActivity.getSharedPreferences(mainActivity.getApplicationInfo().packageName, Context.MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,18 +10,12 @@ public class TxService extends ServiceBase {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final SingleLiveEvent _clearSendEvent = new SingleLiveEvent();
|
|
||||||
public SingleLiveEvent clearSendEvent = _clearSendEvent;
|
|
||||||
|
|
||||||
public TxService(MainActivity mainActivity, MoneroHandlerThread thread) {
|
public TxService(MainActivity mainActivity, MoneroHandlerThread thread) {
|
||||||
super(mainActivity, thread);
|
super(mainActivity, thread);
|
||||||
instance = this;
|
instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendTx(String address, String amount, boolean sendAll) {
|
public boolean sendTx(String address, String amount, boolean sendAll) {
|
||||||
boolean success = this.getThread().sendTx(address, amount, sendAll);
|
return this.getThread().sendTx(address, amount, sendAll);
|
||||||
if (success) {
|
|
||||||
_clearSendEvent.call();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.m2049r.xmrwallet.util;
|
||||||
|
|
||||||
|
public class Constants {
|
||||||
|
public static final String WALLET_NAME = "xmr_wallet";
|
||||||
|
public static final String MNEMONIC_LANGUAGE = "English";
|
||||||
|
public static final String PREF_USES_PASSWORD = "pref_uses_password";
|
||||||
|
}
|
|
@ -1,61 +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 com.m2049r.xmrwallet.util;
|
|
||||||
|
|
||||||
import android.app.KeyguardManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.hardware.fingerprint.FingerprintManager;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.CancellationSignal;
|
|
||||||
|
|
||||||
public class FingerprintHelper {
|
|
||||||
|
|
||||||
public static boolean isDeviceSupported(Context context) {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
FingerprintManager fingerprintManager = context.getSystemService(FingerprintManager.class);
|
|
||||||
KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
|
|
||||||
|
|
||||||
return (keyguardManager != null) && (fingerprintManager != null) &&
|
|
||||||
keyguardManager.isKeyguardSecure() &&
|
|
||||||
fingerprintManager.isHardwareDetected() &&
|
|
||||||
fingerprintManager.hasEnrolledFingerprints();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isFingerPassValid(Context context, String wallet) {
|
|
||||||
try {
|
|
||||||
KeyStoreHelper.loadWalletUserPass(context, wallet);
|
|
||||||
return true;
|
|
||||||
} catch (KeyStoreHelper.BrokenPasswordStoreException ex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void authenticate(Context context, CancellationSignal cancelSignal,
|
|
||||||
FingerprintManager.AuthenticationCallback callback) {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FingerprintManager manager = context.getSystemService(FingerprintManager.class);
|
|
||||||
if (manager != null) {
|
|
||||||
manager.authenticate(null, cancelSignal, 0, callback, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -295,22 +295,6 @@ public class Helper {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static public void setMoneroHome(Context context) {
|
|
||||||
try {
|
|
||||||
String home = getStorage(context, MONERO_DIR).getAbsolutePath();
|
|
||||||
Os.setenv("HOME", home, true);
|
|
||||||
} catch (ErrnoException ex) {
|
|
||||||
throw new IllegalStateException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static public void initLogger(Context context) {
|
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
initLogger(context, WalletManager.LOGLEVEL_DEBUG);
|
|
||||||
}
|
|
||||||
// no logger if not debug
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ?
|
// TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ?
|
||||||
static public void initLogger(Context context, int level) {
|
static public void initLogger(Context context, int level) {
|
||||||
String home = getStorage(context, MONERO_DIR).getAbsolutePath();
|
String home = getStorage(context, MONERO_DIR).getAbsolutePath();
|
||||||
|
@ -318,88 +302,4 @@ public class Helper {
|
||||||
if (level >= WalletManager.LOGLEVEL_SILENT)
|
if (level >= WalletManager.LOGLEVEL_SILENT)
|
||||||
WalletManager.setLogLevel(level);
|
WalletManager.setLogLevel(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
static public boolean useCrazyPass(Context context) {
|
|
||||||
File flagFile = new File(getWalletRoot(context), NOCRAZYPASS_FLAGFILE);
|
|
||||||
return !flagFile.exists();
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to figure out what the real wallet password is given the user password
|
|
||||||
// which could be the actual wallet password or a (maybe malformed) CrAzYpass
|
|
||||||
// or the password used to derive the CrAzYpass for the wallet
|
|
||||||
static public String getWalletPassword(Context context, String walletName, String password) {
|
|
||||||
String walletPath = new File(getWalletRoot(context), walletName + ".keys").getAbsolutePath();
|
|
||||||
|
|
||||||
// try with entered password (which could be a legacy password or a CrAzYpass)
|
|
||||||
if (WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, password)) {
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
|
|
||||||
// maybe this is a malformed CrAzYpass?
|
|
||||||
String possibleCrazyPass = CrazyPassEncoder.reformat(password);
|
|
||||||
if (possibleCrazyPass != null) { // looks like a CrAzYpass
|
|
||||||
if (WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, possibleCrazyPass)) {
|
|
||||||
return possibleCrazyPass;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate & try with CrAzYpass
|
|
||||||
String crazyPass = KeyStoreHelper.getCrazyPass(context, password);
|
|
||||||
if (WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, crazyPass)) {
|
|
||||||
return crazyPass;
|
|
||||||
}
|
|
||||||
|
|
||||||
// or maybe it is a broken CrAzYpass? (of which we have two variants)
|
|
||||||
String brokenCrazyPass2 = KeyStoreHelper.getBrokenCrazyPass(context, password, 2);
|
|
||||||
if ((brokenCrazyPass2 != null)
|
|
||||||
&& WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, brokenCrazyPass2)) {
|
|
||||||
return brokenCrazyPass2;
|
|
||||||
}
|
|
||||||
String brokenCrazyPass1 = KeyStoreHelper.getBrokenCrazyPass(context, password, 1);
|
|
||||||
if ((brokenCrazyPass1 != null)
|
|
||||||
&& WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, brokenCrazyPass1)) {
|
|
||||||
return brokenCrazyPass1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static AlertDialog openDialog = null; // for preventing opening of multiple dialogs
|
|
||||||
static AsyncTask<Void, Void, Boolean> passwordTask = null;
|
|
||||||
|
|
||||||
public interface PasswordAction {
|
|
||||||
void act(String walletName, String password, boolean fingerprintUsed);
|
|
||||||
|
|
||||||
void fail(String walletName);
|
|
||||||
}
|
|
||||||
|
|
||||||
static private boolean processPasswordEntry(Context context, String walletName, String pass, boolean fingerprintUsed, PasswordAction action) {
|
|
||||||
String walletPassword = Helper.getWalletPassword(context, walletName, pass);
|
|
||||||
if (walletPassword != null) {
|
|
||||||
action.act(walletName, walletPassword, fingerprintUsed);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
action.fail(walletName);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Action {
|
|
||||||
boolean run();
|
|
||||||
}
|
|
||||||
|
|
||||||
static public boolean runWithNetwork(Action action) {
|
|
||||||
StrictMode.ThreadPolicy currentPolicy = StrictMode.getThreadPolicy();
|
|
||||||
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();
|
|
||||||
StrictMode.setThreadPolicy(policy);
|
|
||||||
try {
|
|
||||||
return action.run();
|
|
||||||
} finally {
|
|
||||||
StrictMode.setThreadPolicy(currentPolicy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static public boolean preventScreenshot() {
|
|
||||||
return !(BuildConfig.DEBUG || BuildConfig.FLAVOR_type.equals("alpha"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,55 +62,6 @@ public class KeyStoreHelper {
|
||||||
System.loadLibrary("monerujo");
|
System.loadLibrary("monerujo");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static native byte[] slowHash(byte[] data, int brokenVariant);
|
|
||||||
|
|
||||||
static final private String RSA_ALIAS = "MonerujoRSA";
|
|
||||||
|
|
||||||
private static String getCrazyPass(Context context, String password, int brokenVariant) {
|
|
||||||
byte[] data = password.getBytes(StandardCharsets.UTF_8);
|
|
||||||
byte[] sig = null;
|
|
||||||
try {
|
|
||||||
KeyStoreHelper.createKeys(context, RSA_ALIAS);
|
|
||||||
sig = KeyStoreHelper.signData(RSA_ALIAS, data);
|
|
||||||
byte[] hash = slowHash(sig, brokenVariant);
|
|
||||||
if (hash == null) {
|
|
||||||
throw new IllegalStateException("Slow Hash is null!");
|
|
||||||
}
|
|
||||||
return CrazyPassEncoder.encode(hash);
|
|
||||||
} catch (NoSuchProviderException | NoSuchAlgorithmException |
|
|
||||||
InvalidAlgorithmParameterException | KeyStoreException |
|
|
||||||
InvalidKeyException | SignatureException ex) {
|
|
||||||
throw new IllegalStateException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getCrazyPass(Context context, String password) {
|
|
||||||
if (Helper.useCrazyPass(context))
|
|
||||||
return getCrazyPass(context, password, 0);
|
|
||||||
else
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getBrokenCrazyPass(Context context, String password, int brokenVariant) {
|
|
||||||
// due to a link bug in the initial implementation, some crazypasses were built with
|
|
||||||
// prehash & variant == 1
|
|
||||||
// since there are wallets out there, we need to keep this here
|
|
||||||
// yes, it's a mess
|
|
||||||
if (isArm32() && (brokenVariant != 2)) return null;
|
|
||||||
return getCrazyPass(context, password, brokenVariant);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Boolean isArm32 = null;
|
|
||||||
|
|
||||||
public static boolean isArm32() {
|
|
||||||
if (isArm32 != null) return isArm32;
|
|
||||||
synchronized (KeyStoreException.class) {
|
|
||||||
if (isArm32 != null) return isArm32;
|
|
||||||
isArm32 = Build.SUPPORTED_ABIS[0].equals("armeabi-v7a");
|
|
||||||
return isArm32;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean saveWalletUserPass(@NonNull Context context, String wallet, String password) {
|
public static boolean saveWalletUserPass(@NonNull Context context, String wallet, String password) {
|
||||||
String walletKeyAlias = SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet;
|
String walletKeyAlias = SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet;
|
||||||
byte[] data = password.getBytes(StandardCharsets.UTF_8);
|
byte[] data = password.getBytes(StandardCharsets.UTF_8);
|
||||||
|
@ -141,38 +92,6 @@ public class KeyStoreHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasStoredPasswords(@NonNull Context context) {
|
|
||||||
SharedPreferences prefs = context.getSharedPreferences(SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE);
|
|
||||||
return prefs.getAll().size() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String loadWalletUserPass(@NonNull Context context, String wallet) throws BrokenPasswordStoreException {
|
|
||||||
String walletKeyAlias = SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet;
|
|
||||||
String encoded = context.getSharedPreferences(SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE)
|
|
||||||
.getString(wallet, "");
|
|
||||||
if (encoded.isEmpty()) throw new BrokenPasswordStoreException();
|
|
||||||
byte[] data = Base64.decode(encoded, Base64.DEFAULT);
|
|
||||||
byte[] decrypted = KeyStoreHelper.decrypt(walletKeyAlias, data);
|
|
||||||
if (decrypted == null) throw new BrokenPasswordStoreException();
|
|
||||||
return new String(decrypted, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void removeWalletUserPass(Context context, String wallet) {
|
|
||||||
String walletKeyAlias = SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet;
|
|
||||||
try {
|
|
||||||
KeyStoreHelper.deleteKeys(walletKeyAlias);
|
|
||||||
} catch (KeyStoreException ex) {
|
|
||||||
Timber.w(ex);
|
|
||||||
}
|
|
||||||
context.getSharedPreferences(SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE).edit()
|
|
||||||
.remove(wallet).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void copyWalletUserPass(Context context, String srcWallet, String dstWallet) throws BrokenPasswordStoreException {
|
|
||||||
final String pass = loadWalletUserPass(context, srcWallet);
|
|
||||||
saveWalletUserPass(context, dstWallet, pass);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a public and private key and stores it using the Android Key
|
* Creates a public and private key and stores it using the Android Key
|
||||||
* Store, so that only this application will be able to access the keys.
|
* Store, so that only this application will be able to access the keys.
|
||||||
|
|
|
@ -6,14 +6,40 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".fragment.settings.SettingsFragment">
|
tools:context=".fragment.settings.SettingsFragment">
|
||||||
|
|
||||||
<ImageView
|
<EditText
|
||||||
android:id="@+id/monero_logo_imageview"
|
android:id="@+id/wallet_password_edittext"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:src="@drawable/ic_monero_qr"
|
android:layout_marginStart="24dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:layout_marginEnd="12dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
android:layout_marginBottom="32dp"
|
||||||
|
android:hint="Password (optional)"
|
||||||
|
android:inputType="textPassword"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/wallet_seed_edittext"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/wallet_seed_edittext"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:hint="Recovery phrase (optional)"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/create_wallet_button"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/create_wallet_button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:text="@string/create_wallet"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/wallet_seed_edittext"
|
||||||
app:layout_constraintStart_toStartOf="parent"/>
|
app:layout_constraintStart_toStartOf="parent"/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
49
app/src/main/res/layout/password_bottom_sheet_dialog.xml
Normal file
49
app/src/main/res/layout/password_bottom_sheet_dialog.xml
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/wallet_password_edittext"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:hint="Password"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/unlock_wallet_button"/>
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/paste_password_imagebutton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:src="@drawable/ic_content_paste_24dp"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/wallet_password_edittext"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/wallet_password_edittext"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/wallet_password_edittext"/>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/unlock_wallet_button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:text="Unlock"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/wallet_password_edittext"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
|
@ -16,6 +16,13 @@
|
||||||
android:name="address"
|
android:name="address"
|
||||||
app:argType="string" />
|
app:argType="string" />
|
||||||
</action>
|
</action>
|
||||||
|
<action
|
||||||
|
android:id="@+id/nav_to_onboarding"
|
||||||
|
app:destination="@id/onboarding_fragment">
|
||||||
|
<argument
|
||||||
|
android:name="address"
|
||||||
|
app:argType="string" />
|
||||||
|
</action>
|
||||||
</fragment>
|
</fragment>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/settings_fragment"
|
android:id="@+id/settings_fragment"
|
||||||
|
@ -23,4 +30,10 @@
|
||||||
android:label="fragment_send_amount"
|
android:label="fragment_send_amount"
|
||||||
tools:layout="@layout/fragment_settings">
|
tools:layout="@layout/fragment_settings">
|
||||||
</fragment>
|
</fragment>
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/onboarding_fragment"
|
||||||
|
android:name="com.m2049r.xmrwallet.fragment.onboarding.OnboardingFragment"
|
||||||
|
android:label="fragment_onboarding"
|
||||||
|
tools:layout="@layout/fragment_settings">
|
||||||
|
</fragment>
|
||||||
</navigation>
|
</navigation>
|
|
@ -541,4 +541,7 @@
|
||||||
<string name="send_amount_invalid">Please enter a valid amount</string>
|
<string name="send_amount_invalid">Please enter a valid amount</string>
|
||||||
<string name="send_max">Send Max</string>
|
<string name="send_max">Send Max</string>
|
||||||
<string name="undo">Undo</string>
|
<string name="undo">Undo</string>
|
||||||
|
<string name="error_sending_tx">Error sending tx</string>
|
||||||
|
<string name="create_wallet">Create wallet</string>
|
||||||
|
<string name="invalid_mnemonic_code">Invalid mnemonic</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue