Generate Wallet Fragment added

This commit is contained in:
m2049r 2017-08-15 23:59:41 +02:00
parent 6611539491
commit 34941c599a
9 changed files with 455 additions and 33 deletions

View File

@ -463,7 +463,7 @@ Java_com_m2049r_xmrwallet_model_WalletManager_resolveOpenAlias(JNIEnv *env, jobj
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir); //TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
// actually a WalletManager function, but logically in Wallet // actually a WalletManager function, but logically in onWalletSelected
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_closeJ(JNIEnv *env, jobject instance, Java_com_m2049r_xmrwallet_model_WalletManager_closeJ(JNIEnv *env, jobject instance,
jobject walletInstance) { jobject walletInstance) {

View File

@ -0,0 +1,179 @@
package com.m2049r.xmrwallet;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
public class GenerateFragment extends Fragment {
EditText etWalletName;
EditText etWalletPassword;
Button bGenerate;
LinearLayout llAccept;
TextView tvWalletMnemonic;
Button bAccept;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.gen_fragment, container, false);
etWalletName = (EditText) view.findViewById(R.id.etWalletName);
etWalletPassword = (EditText) view.findViewById(R.id.etWalletPassword);
bGenerate = (Button) view.findViewById(R.id.bGenerate);
llAccept = (LinearLayout) view.findViewById(R.id.llAccept);
tvWalletMnemonic = (TextView) view.findViewById(R.id.tvWalletMnemonic);
bAccept = (Button) view.findViewById(R.id.bAccept);
etWalletName.requestFocus();
etWalletName.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
setGenerateEnabled();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
etWalletName.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(etWalletName, InputMethodManager.SHOW_IMPLICIT);
}
});
etWalletName.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
etWalletPassword.requestFocus();
return false;
}
return false;
}
});
etWalletPassword.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(etWalletPassword, InputMethodManager.SHOW_IMPLICIT);
}
});
etWalletPassword.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
generateWallet();
return false;
}
return false;
}
});
bGenerate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
generateWallet();
}
});
bAccept.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
acceptWallet();
}
});
bAccept.setEnabled(false);
return view;
}
private void generateWallet() {
String name = etWalletName.getText().toString();
if (name.length() == 0) return;
File walletFile = new File(activityCallback.getStorageRoot(), name + ".keys");
if (walletFile.exists()) {
Toast.makeText(getActivity(), getString(R.string.generate_wallet_exists), Toast.LENGTH_LONG).show();
return;
}
String password = etWalletPassword.getText().toString();
bGenerate.setEnabled(false);
activityCallback.onGenerate(name, password);
}
private void acceptWallet() {
String name = etWalletName.getText().toString();
String password = etWalletPassword.getText().toString();
bAccept.setEnabled(false);
activityCallback.onAccept(name, password);
}
private void setGenerateEnabled() {
bGenerate.setEnabled(etWalletName.length() > 0);
}
@Override
public void onResume() {
super.onResume();
setGenerateEnabled();
etWalletName.requestFocus();
InputMethodManager imgr = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imgr.showSoftInput(etWalletName, InputMethodManager.SHOW_IMPLICIT);
}
public void showMnemonic(String mnemonic) {
setGenerateEnabled();
if (mnemonic.length() > 0) {
tvWalletMnemonic.setText(mnemonic);
bAccept.setEnabled(true);
} else {
tvWalletMnemonic.setText(getActivity().getString(R.string.generate_seed));
bAccept.setEnabled(false);
}
}
GenerateFragment.Listener activityCallback;
// Container Activity must implement this interface
public interface Listener {
void onGenerate(String name, String password);
void onAccept(String name, String password);
File getStorageRoot();
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof GenerateFragment.Listener) {
this.activityCallback = (GenerateFragment.Listener) context;
} else {
throw new ClassCastException(context.toString()
+ " must implement Listener");
}
}
}

View File

@ -19,6 +19,7 @@ package com.m2049r.xmrwallet;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Fragment; import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
@ -36,12 +37,15 @@ import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.MoneroHandlerThread;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import java.io.File; import java.io.File;
public class LoginActivity extends Activity implements LoginFragment.LoginFragmentListener { public class LoginActivity extends Activity
implements LoginFragment.Listener, GenerateFragment.Listener {
static final String TAG = "LoginActivity"; static final String TAG = "LoginActivity";
static final int DAEMON_TIMEOUT = 500; // deamon must respond in 500ms static final int DAEMON_TIMEOUT = 500; // deamon must respond in 500ms
@ -62,7 +66,15 @@ public class LoginActivity extends Activity implements LoginFragment.LoginFragme
// adapted from http://www.mkyong.com/android/android-prompt-user-input-dialog-example/ // adapted from http://www.mkyong.com/android/android-prompt-user-input-dialog-example/
@Override @Override
public void promptPassword(final String wallet) { public void onWalletSelected(final String wallet) {
if (wallet.toLowerCase().startsWith("test")) {
startGenerateFragment();
} else {
promptPassword(wallet);
}
}
void promptPassword(final String wallet) {
Context context = LoginActivity.this; Context context = LoginActivity.this;
LayoutInflater li = LayoutInflater.from(context); LayoutInflater li = LayoutInflater.from(context);
View promptsView = li.inflate(R.layout.prompt_password, null); View promptsView = li.inflate(R.layout.prompt_password, null);
@ -130,7 +142,7 @@ public class LoginActivity extends Activity implements LoginFragment.LoginFragme
} }
//////////////////////////////////////// ////////////////////////////////////////
// LoginFragment.LoginFragmentListener // LoginFragment.Listener
//////////////////////////////////////// ////////////////////////////////////////
@Override @Override
public SharedPreferences getPrefs() { public SharedPreferences getPrefs() {
@ -190,6 +202,126 @@ public class LoginActivity extends Activity implements LoginFragment.LoginFragme
Fragment fragment = new LoginFragment(); Fragment fragment = new LoginFragment();
getFragmentManager().beginTransaction() getFragmentManager().beginTransaction()
.add(R.id.fragment_container, fragment).commit(); .add(R.id.fragment_container, fragment).commit();
Log.d(TAG, "fragment added"); Log.d(TAG, "LoginFragment added");
}
void startGenerateFragment() {
replaceFragment(new GenerateFragment());
Log.d(TAG, "GenerateFragment placed");
}
void replaceFragment(Fragment newFragment) {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
transaction.commit();
}
//////////////////////////////////////////
// GenerateFragment.Listener
//////////////////////////////////////////
static final String MNEMONIC_LANGUAGE = "English"; // see mnemonics/electrum-words.cpp for more
@Override
public void onGenerate(final String name, final String password) {
final GenerateFragment genFragment = (GenerateFragment)
getFragmentManager().findFragmentById(R.id.fragment_container);
File newWalletFolder = new File(getStorageRoot(), ".new");
if (!newWalletFolder.exists()) {
if (!newWalletFolder.mkdir()) {
Log.e(TAG, "Cannot create new wallet dir " + newWalletFolder.getAbsolutePath());
genFragment.showMnemonic("");
return;
}
}
if (!newWalletFolder.isDirectory()) {
Log.e(TAG, "New wallet dir " + newWalletFolder.getAbsolutePath() + "is not a directory");
genFragment.showMnemonic("");
return;
}
File cache = new File(newWalletFolder, name);
cache.delete();
File keys = new File(newWalletFolder, name + ".keys");
keys.delete();
File address = new File(newWalletFolder, name + ".address.txt");
address.delete();
if (cache.exists() || keys.exists() || address.exists()) {
Log.e(TAG, "Cannot remove all old wallet files: " + cache.getAbsolutePath());
genFragment.showMnemonic("");
return;
}
final String newWalletPath = new File(newWalletFolder, name).getAbsolutePath();
new Thread(null,
new Runnable() {
@Override
public void run() {
Log.d(TAG, "creating wallet " + newWalletPath);
Wallet newWallet = WalletManager.getInstance()
.createWallet(newWalletPath, password, MNEMONIC_LANGUAGE);
Log.d(TAG, "wallet created");
Log.d(TAG, "Created " + newWallet.getAddress());
Log.d(TAG, "Seed " + newWallet.getSeed() + ".");
final String mnemonic = newWallet.getSeed();
newWallet.close();
runOnUiThread(new Runnable() {
public void run() {
genFragment.showMnemonic(mnemonic);
}
});
}
}
, "CreateWallet", MoneroHandlerThread.THREAD_STACK_SIZE).start();
}
@Override
public void onAccept(final String name, final String password) {
File newWalletFolder = new File(getStorageRoot(), ".new");
if (!newWalletFolder.isDirectory()) {
Log.e(TAG, "New wallet dir " + newWalletFolder.getAbsolutePath() + "is not a directory");
return;
}
final String newWalletPath = new File(newWalletFolder, name).getAbsolutePath();
new Thread(null,
new Runnable() {
@Override
public void run() {
Log.d(TAG, "opening wallet " + newWalletPath);
Wallet newWallet = WalletManager.getInstance()
.openWallet(newWalletPath, password);
Wallet.Status status = newWallet.getStatus();
Log.d(TAG, "wallet opened " + newWallet.getStatus());
if (status != Wallet.Status.Status_Ok) {
Log.e(TAG, "New wallet is " + status.toString());
runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(LoginActivity.this,
getString(R.string.generate_wallet_create_failed), Toast.LENGTH_SHORT).show();
}
});
return;
}
final String walletPath = new File(getStorageRoot(), name).getAbsolutePath();
final boolean rc = newWallet.store(walletPath);
Log.d(TAG, "wallet stored with rc=" + rc);
newWallet.close();
Log.d(TAG, "wallet closed");
runOnUiThread(new Runnable() {
public void run() {
if (rc) {
getFragmentManager().popBackStack();
Toast.makeText(LoginActivity.this,
getString(R.string.generate_wallet_created), Toast.LENGTH_SHORT).show();
} else {
Log.e(TAG, "Wallet store failed to " + walletPath);
Toast.makeText(LoginActivity.this,
getString(R.string.generate_wallet_create_failed), Toast.LENGTH_SHORT).show();
}
}
});
}
}
, "AcceptWallet", MoneroHandlerThread.THREAD_STACK_SIZE).start();
} }
} }

View File

@ -45,38 +45,49 @@ import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public class LoginFragment extends Fragment { public class LoginFragment extends Fragment {
private static final String TAG = "LoginFragment"; private static final String TAG = "LoginFragment";
public static final int WALLETNAME_PREAMBLE_LENGTH = "[123456] ".length();
ListView listView; ListView listView;
List<String> walletList = new ArrayList<>(); Set<String> walletList = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.substring(WALLETNAME_PREAMBLE_LENGTH).toLowerCase()
.compareTo(o2.substring(WALLETNAME_PREAMBLE_LENGTH).toLowerCase());
}
});
List<String> displayedList = new ArrayList<>(); List<String> displayedList = new ArrayList<>();
ToggleButton tbMainNet; ToggleButton tbMainNet;
EditText etDaemonAddress; EditText etDaemonAddress;
LoginFragment.LoginFragmentListener activityCallback; Listener activityCallback;
// Container Activity must implement this interface // Container Activity must implement this interface
public interface LoginFragmentListener { public interface Listener {
SharedPreferences getPrefs(); SharedPreferences getPrefs();
File getStorageRoot(); File getStorageRoot();
void promptPassword(final String wallet); void onWalletSelected(final String wallet);
} }
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);
if (context instanceof LoginFragment.LoginFragmentListener) { if (context instanceof Listener) {
this.activityCallback = (LoginFragment.LoginFragmentListener) context; this.activityCallback = (Listener) context;
} else { } else {
throw new ClassCastException(context.toString() throw new ClassCastException(context.toString()
+ " must implement WalletFragmentListener"); + " must implement Listener");
} }
} }
@ -148,14 +159,13 @@ public class LoginFragment extends Fragment {
} }
String itemValue = (String) listView.getItemAtPosition(position); String itemValue = (String) listView.getItemAtPosition(position);
if ((isMainNet() && itemValue.charAt(1) != '4') String x = isMainNet() ? "4" : "9A";
|| (!isMainNet() && itemValue.charAt(1) != '9')) { if (x.indexOf(itemValue.charAt(1)) < 0) {
Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show(); Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show();
return; return;
} }
final int preambleLength = "[123456] ".length(); if (itemValue.length() <= (WALLETNAME_PREAMBLE_LENGTH)) {
if (itemValue.length() <= (preambleLength)) {
Toast.makeText(getActivity(), getString(R.string.panic), Toast.LENGTH_LONG).show(); Toast.makeText(getActivity(), getString(R.string.panic), Toast.LENGTH_LONG).show();
return; return;
} }
@ -167,8 +177,8 @@ public class LoginFragment extends Fragment {
// looking good // looking good
savePrefs(false); savePrefs(false);
String wallet = itemValue.substring(preambleLength); String wallet = itemValue.substring(WALLETNAME_PREAMBLE_LENGTH);
activityCallback.promptPassword(wallet); activityCallback.onWalletSelected(wallet);
} }
}); });
loadList(); loadList();
@ -177,9 +187,10 @@ public class LoginFragment extends Fragment {
private void filterList() { private void filterList() {
displayedList.clear(); displayedList.clear();
char x = isMainNet() ? '4' : '9'; String x = isMainNet() ? "4" : "9A";
for (String s : walletList) { for (String s : walletList) {
if (s.charAt(1) == x) displayedList.add(s); Log.d(TAG, "filtering " + s);
if (x.indexOf(s.charAt(1)) >= 0) displayedList.add(s);
} }
} }

View File

@ -31,7 +31,7 @@ import android.widget.Toast;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.service.WalletService; import com.m2049r.xmrwallet.service.WalletService;
public class WalletActivity extends Activity implements WalletFragment.WalletFragmentListener, public class WalletActivity extends Activity implements WalletFragment.Listener,
WalletService.Observer { WalletService.Observer {
private static final String TAG = "WalletActivity"; private static final String TAG = "WalletActivity";
@ -212,7 +212,7 @@ public class WalletActivity extends Activity implements WalletFragment.WalletFra
} }
////////////////////////////////////////// //////////////////////////////////////////
// WalletFragment.WalletFragmentListener // WalletFragment.Listener
////////////////////////////////////////// //////////////////////////////////////////
@Override @Override

View File

@ -188,10 +188,10 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
connectionStatusView.setText(net + " " + daemonConnected.toString().substring(17)); connectionStatusView.setText(net + " " + daemonConnected.toString().substring(17));
} }
WalletFragmentListener activityCallback; Listener activityCallback;
// Container Activity must implement this interface // Container Activity must implement this interface
public interface WalletFragmentListener { public interface Listener {
boolean hasBoundService(); boolean hasBoundService();
Wallet.ConnectionStatus getConnectionStatus(); Wallet.ConnectionStatus getConnectionStatus();
@ -205,15 +205,11 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);
if (context instanceof WalletFragmentListener) { if (context instanceof Listener) {
this.activityCallback = (WalletFragmentListener) context; this.activityCallback = (Listener) context;
} else { } else {
throw new ClassCastException(context.toString() throw new ClassCastException(context.toString()
+ " must implement WalletFragmentListener"); + " must implement Listener");
} }
} }
private void runOnUiThread(Runnable runnable) {
if (isAdded()) getActivity().runOnUiThread(runnable);
}
} }

View File

@ -57,7 +57,7 @@ public class WalletManager {
private void manageWallet(String walletId, Wallet wallet) { private void manageWallet(String walletId, Wallet wallet) {
if (getWallet(walletId) != null) { if (getWallet(walletId) != null) {
throw new IllegalStateException("Wallet already under management!"); throw new IllegalStateException(walletId + " already under management!");
} }
Log.d(TAG, "Managing " + walletId); Log.d(TAG, "Managing " + walletId);
managedWallets.put(walletId, wallet); managedWallets.put(walletId, wallet);
@ -65,7 +65,7 @@ public class WalletManager {
private void unmanageWallet(String walletId) { private void unmanageWallet(String walletId) {
if (getWallet(walletId) == null) { if (getWallet(walletId) == null) {
throw new IllegalStateException("Wallet not under management!"); throw new IllegalStateException(walletId + " not under management!");
} }
Log.d(TAG, "Unmanaging " + walletId); Log.d(TAG, "Unmanaging " + walletId);
managedWallets.remove(walletId); managedWallets.remove(walletId);

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:gravity="center"
android:text="@string/generate_title"
android:textSize="30sp" />
<EditText
android:id="@+id/etWalletName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:backgroundTint="@color/colorPrimary"
android:focusable="true"
android:gravity="center"
android:hint="@string/generate_name"
android:imeOptions="actionDone"
android:inputType="text"
android:maxLines="1"
android:textIsSelectable="true"
android:textSize="24sp" />
<EditText
android:id="@+id/etWalletPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:backgroundTint="@color/colorPrimary"
android:focusable="true"
android:gravity="center"
android:hint="@string/generate_password"
android:imeOptions="actionDone"
android:inputType="text"
android:maxLines="1"
android:textIsSelectable="true"
android:textSize="24sp" />
<Button
android:id="@+id/bGenerate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="8dp"
android:background="@color/colorPrimary"
android:text="@string/generate_buttonGenerate" />
<LinearLayout
android:id="@+id/llAccept"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:visibility="visible">
<TextView
android:id="@+id/tvWalletMnemonic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/generate_seed"
android:textAlignment="center"
android:textColor="@color/menu_background_color"
android:textSize="24sp" />
<Button
android:id="@+id/bAccept"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="8dp"
android:background="@color/colorPrimary"
android:text="@string/generate_buttonAccept" />
</LinearLayout>
</LinearLayout>

View File

@ -43,4 +43,14 @@
<string name="message_strorage_not_writable">External Storage is not writable! Panic!</string> <string name="message_strorage_not_writable">External Storage is not writable! Panic!</string>
<string name="message_strorage_not_permitted">We really need those External Storage permissions!</string> <string name="message_strorage_not_permitted">We really need those External Storage permissions!</string>
<string name="generate_title">Generate Wallet</string>
<string name="generate_name">Wallet Name</string>
<string name="generate_password">Wallet Password</string>
<string name="generate_buttonGenerate">Do it already!</string>
<string name="generate_seed">Mnemonic Seed</string>
<string name="generate_buttonAccept">I have noted this mnemonic seed\nNow, I want to loose all my money!</string>
<string name="generate_wallet_exists">Wallet exists!\nChoose another name</string>
<string name="generate_wallet_created">Wallet created</string>
<string name="generate_wallet_create_failed">Wallet create failed</string>
</resources> </resources>