Cleanup some Kotlin code, convert HomeFragment to Kotlin, and dialogs

This commit is contained in:
pokkst 2023-12-06 15:33:30 -06:00
parent 97d39a3cb4
commit d38a2e1306
No known key found for this signature in database
GPG key ID: EC4FAAA66859FAA4
36 changed files with 1195 additions and 1367 deletions

View file

@ -245,7 +245,6 @@ class Node {
private var DEFAULT_LEVIN_PORT = 0 private var DEFAULT_LEVIN_PORT = 0
private var DEFAULT_RPC_PORT = 0 private var DEFAULT_RPC_PORT = 0
@JvmStatic
fun fromString(nodeString: String?): Node? { fun fromString(nodeString: String?): Node? {
return try { return try {
Node(nodeString) Node(nodeString)

View file

@ -23,7 +23,6 @@ class Subaddress(
@JvmField val address: String, @JvmField val address: String,
val label: String val label: String
) : Comparable<Subaddress> { ) : Comparable<Subaddress> {
@JvmField
var amount: Long = 0 var amount: Long = 0
override fun compareTo(other: Subaddress): Int { // newer is < override fun compareTo(other: Subaddress): Int { // newer is <

View file

@ -1,106 +0,0 @@
/*
* Copyright (c) 2017 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.mynero.wallet.data
import android.os.Parcel
import android.os.Parcelable
import android.os.Parcelable.Creator
import net.mynero.wallet.model.PendingTransaction
import net.mynero.wallet.model.Wallet
import net.mynero.wallet.util.Helper
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
class TxData : Parcelable {
var destinationAddress: String? = null
var amount: Long = 0
var mixin = 0
var priority: PendingTransaction.Priority? = null
var userNotes: UserNotes? = null
var preferredInputs: ArrayList<String>? = null
private set
constructor()
constructor(txData: TxData) {
destinationAddress = txData.destinationAddress
amount = txData.amount
mixin = txData.mixin
priority = txData.priority
preferredInputs = txData.preferredInputs
}
constructor(
dstAddr: String?,
amount: Long,
mixin: Int,
priority: PendingTransaction.Priority?,
preferredInputs: ArrayList<String>?
) {
destinationAddress = dstAddr
this.amount = amount
this.mixin = mixin
this.priority = priority
this.preferredInputs = preferredInputs
}
protected constructor(`in`: Parcel) {
destinationAddress = `in`.readString()
amount = `in`.readLong()
mixin = `in`.readInt()
priority = PendingTransaction.Priority.fromInteger(`in`.readInt())
`in`.readStringList(preferredInputs!!)
}
fun setAmount(amount: Double) {
this.amount = Wallet.getAmountFromDouble(amount)
}
val amountAsDouble: Double
get() = 1.0 * amount / Helper.ONE_XMR
override fun writeToParcel(out: Parcel, flags: Int) {
out.writeString(destinationAddress)
out.writeLong(amount)
out.writeInt(mixin)
out.writeInt(priority!!.value)
}
override fun describeContents(): Int {
return 0
}
override fun toString(): String {
val sb = StringBuffer()
sb.append("dstAddr:")
sb.append(destinationAddress)
sb.append(",amount:")
sb.append(amount)
sb.append(",mixin:")
sb.append(mixin)
sb.append(",priority:")
sb.append(priority)
return sb.toString()
}
companion object CREATOR : Creator<TxData> {
override fun createFromParcel(parcel: Parcel): TxData {
return TxData(parcel)
}
override fun newArray(size: Int): Array<TxData?> {
return arrayOfNulls(size)
}
}
}

View file

@ -71,7 +71,7 @@ class UserNotes(txNotes: String?) {
sb.append(",") sb.append(",")
sb.append(xmrtoDestination) sb.append(xmrtoDestination)
sb.append("}") sb.append("}")
if (note != null && !note!!.isEmpty()) sb.append(" ") if (note != null && note?.isNotEmpty() == true) sb.append(" ")
} }
sb.append(note) sb.append(note)
return sb.toString() return sb.toString()

View file

@ -1,155 +0,0 @@
package net.mynero.wallet.fragment.dialog;
import android.content.Context;
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.ImageButton;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import net.mynero.wallet.R;
import net.mynero.wallet.data.Node;
import net.mynero.wallet.service.PrefService;
import net.mynero.wallet.util.Constants;
import net.mynero.wallet.util.Helper;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class AddNodeBottomSheetDialog extends BottomSheetDialogFragment {
public AddNodeListener listener = null;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.add_node_bottom_sheet_dialog, null);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Button addNodeButton = view.findViewById(R.id.add_node_button);
EditText addressEditText = view.findViewById(R.id.address_edittext);
EditText portEditText = view.findViewById(R.id.node_port_edittext);
EditText nodeNameEditText = view.findViewById(R.id.node_name_edittext);
EditText usernameEditText = view.findViewById(R.id.username_edittext);
EditText passwordEditText = view.findViewById(R.id.password_edittext);
ImageButton pastePasswordImageButton = view.findViewById(R.id.paste_password_imagebutton);
usernameEditText.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) {
if (editable.toString().isEmpty()) {
passwordEditText.setText(null);
passwordEditText.setVisibility(View.GONE);
pastePasswordImageButton.setVisibility(View.GONE);
} else {
passwordEditText.setVisibility(View.VISIBLE);
pastePasswordImageButton.setVisibility(View.VISIBLE);
}
}
});
addPasteListener(view, addressEditText, R.id.paste_address_imagebutton);
addPasteListener(view, usernameEditText, R.id.paste_username_imagebutton);
addPasteListener(view, passwordEditText, R.id.paste_password_imagebutton);
addNodeButton.setOnClickListener(view1 -> {
String nodeAddr = addressEditText.getText().toString();
String portString = portEditText.getText().toString();
String name = nodeNameEditText.getText().toString();
String user = usernameEditText.getText().toString();
String pass = passwordEditText.getText().toString();
if (name.isEmpty()) {
Toast.makeText(getContext(), "Enter node name", Toast.LENGTH_SHORT).show();
return;
} else if (nodeAddr.isEmpty() || portString.isEmpty()) {
Toast.makeText(getContext(), "Enter node address", Toast.LENGTH_SHORT).show();
return;
} else if (!user.isEmpty() && pass.isEmpty()) {
Toast.makeText(getContext(), "Enter password", Toast.LENGTH_SHORT).show();
return;
}
JSONObject jsonObject = new JSONObject();
try {
if (!user.isEmpty()) {
jsonObject.put("username", user);
jsonObject.put("password", pass);
}
if (nodeAddr.contains(":") && !nodeAddr.startsWith("[") && !nodeAddr.endsWith("]"))
nodeAddr = "[" + nodeAddr + "]";
jsonObject.put("host", nodeAddr);
jsonObject.put("rpcPort", Integer.parseInt(portString));
jsonObject.put("network", "mainnet");
jsonObject.put("name", name);
addNodeToSaved(jsonObject);
if (listener != null) {
listener.onNodeAdded();
dismiss();
}
} catch (JSONException e) {
throw new RuntimeException(e);
}
});
}
private void addNodeToSaved(JSONObject newNodeJsonObject) throws JSONException {
Node newNode = Node.fromJson(newNodeJsonObject);
String nodesArray = PrefService.getInstance().getString(Constants.PREF_CUSTOM_NODES, "[]");
JSONArray jsonArray = new JSONArray(nodesArray);
boolean exists = false;
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject nodeJsonObject = jsonArray.getJSONObject(i);
Node node = Node.fromJson(nodeJsonObject);
if (node.toNodeString().equals(newNode.toNodeString()))
exists = true;
}
if (!exists) {
jsonArray.put(newNodeJsonObject);
} else {
Toast.makeText(getContext(), "Node already exists", Toast.LENGTH_SHORT).show();
return;
}
PrefService.getInstance().edit().putString(Constants.PREF_CUSTOM_NODES, jsonArray.toString()).apply();
}
private void addPasteListener(View root, EditText editText, int layoutId) {
ImageButton pasteImageButton = root.findViewById(layoutId);
pasteImageButton.setOnClickListener(view1 -> {
Context ctx = getContext();
if (ctx != null) {
editText.setText(Helper.getClipBoardText(ctx));
}
});
}
public interface AddNodeListener {
void onNodeAdded();
}
}

View file

@ -0,0 +1,133 @@
package net.mynero.wallet.fragment.dialog
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.ImageButton
import android.widget.Toast
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import net.mynero.wallet.R
import net.mynero.wallet.data.Node.Companion.fromJson
import net.mynero.wallet.service.PrefService
import net.mynero.wallet.util.Constants
import net.mynero.wallet.util.Helper.getClipBoardText
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
class AddNodeBottomSheetDialog : BottomSheetDialogFragment() {
@JvmField
var listener: AddNodeListener? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.add_node_bottom_sheet_dialog, null)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val addNodeButton = view.findViewById<Button>(R.id.add_node_button)
val addressEditText = view.findViewById<EditText>(R.id.address_edittext)
val portEditText = view.findViewById<EditText>(R.id.node_port_edittext)
val nodeNameEditText = view.findViewById<EditText>(R.id.node_name_edittext)
val usernameEditText = view.findViewById<EditText>(R.id.username_edittext)
val passwordEditText = view.findViewById<EditText>(R.id.password_edittext)
val pastePasswordImageButton =
view.findViewById<ImageButton>(R.id.paste_password_imagebutton)
usernameEditText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
if (editable.toString().isEmpty()) {
passwordEditText.text = null
passwordEditText.visibility = View.GONE
pastePasswordImageButton.visibility = View.GONE
} else {
passwordEditText.visibility = View.VISIBLE
pastePasswordImageButton.visibility = View.VISIBLE
}
}
})
addPasteListener(view, addressEditText, R.id.paste_address_imagebutton)
addPasteListener(view, usernameEditText, R.id.paste_username_imagebutton)
addPasteListener(view, passwordEditText, R.id.paste_password_imagebutton)
addNodeButton.setOnClickListener {
var nodeAddr = addressEditText.text.toString()
val portString = portEditText.text.toString()
val name = nodeNameEditText.text.toString()
val user = usernameEditText.text.toString()
val pass = passwordEditText.text.toString()
if (name.isEmpty()) {
Toast.makeText(context, "Enter node name", Toast.LENGTH_SHORT).show()
return@setOnClickListener
} else if (nodeAddr.isEmpty() || portString.isEmpty()) {
Toast.makeText(context, "Enter node address", Toast.LENGTH_SHORT).show()
return@setOnClickListener
} else if (user.isNotEmpty() && pass.isEmpty()) {
Toast.makeText(context, "Enter password", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
val jsonObject = JSONObject()
try {
if (user.isNotEmpty()) {
jsonObject.put("username", user)
jsonObject.put("password", pass)
}
if (nodeAddr.contains(":") && !nodeAddr.startsWith("[") && !nodeAddr.endsWith("]")) nodeAddr =
"[$nodeAddr]"
jsonObject.put("host", nodeAddr)
jsonObject.put("rpcPort", portString.toInt())
jsonObject.put("network", "mainnet")
jsonObject.put("name", name)
addNodeToSaved(jsonObject)
if (listener != null) {
listener?.onNodeAdded()
dismiss()
}
} catch (e: JSONException) {
throw RuntimeException(e)
}
}
}
@Throws(JSONException::class)
private fun addNodeToSaved(newNodeJsonObject: JSONObject) {
val newNode = fromJson(newNodeJsonObject)
val nodesArray = PrefService.instance?.getString(Constants.PREF_CUSTOM_NODES, "[]")
val jsonArray = JSONArray(nodesArray)
var exists = false
for (i in 0 until jsonArray.length()) {
val nodeJsonObject = jsonArray.getJSONObject(i)
val node = fromJson(nodeJsonObject)
if (node?.toNodeString() == newNode?.toNodeString()) exists = true
}
if (!exists) {
jsonArray.put(newNodeJsonObject)
} else {
Toast.makeText(context, "Node already exists", Toast.LENGTH_SHORT).show()
return
}
PrefService.instance?.edit()?.putString(Constants.PREF_CUSTOM_NODES, jsonArray.toString())?.apply()
}
private fun addPasteListener(root: View, editText: EditText, layoutId: Int) {
val pasteImageButton = root.findViewById<ImageButton>(layoutId)
pasteImageButton.setOnClickListener {
val ctx = context
if (ctx != null) {
editText.setText(getClipBoardText(ctx))
}
}
}
interface AddNodeListener {
fun onNodeAdded()
}
}

View file

@ -1,70 +0,0 @@
package net.mynero.wallet.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 androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import net.mynero.wallet.R;
import net.mynero.wallet.model.Wallet;
import net.mynero.wallet.model.WalletManager;
import net.mynero.wallet.service.AddressService;
import net.mynero.wallet.util.Helper;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Locale;
public class EditAddressLabelBottomSheetDialog extends BottomSheetDialogFragment {
public LabelListener listener = null;
public int addressIndex = 0;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.bottom_sheet_dialog_edit_address_label, null);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Wallet wallet = WalletManager.getInstance().getWallet();
AddressService addressService = AddressService.instance;
ImageButton pasteButton = view.findViewById(R.id.paste_password_imagebutton);
EditText labelEditText = view.findViewById(R.id.wallet_password_edittext);
Button saveLabelButton = view.findViewById(R.id.unlock_wallet_button);
boolean isDate = false;
try {
new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss", Locale.US).parse(wallet.getSubaddressLabel(addressIndex));
isDate = true;
} catch (ParseException ignored) {
}
labelEditText.setText(isDate ? null : wallet.getSubaddressLabel(addressIndex));
pasteButton.setOnClickListener(view1 -> labelEditText.setText(Helper.getClipBoardText(view.getContext())));
saveLabelButton.setOnClickListener(view1 -> {
String label = labelEditText.getText().toString();
if (addressService.getLatestAddressIndex() == addressIndex) {
addressService.freshSubaddress();
}
wallet.setSubaddressLabel(addressIndex, label);
wallet.store();
if (listener != null) {
listener.onDismiss();
}
dismiss();
});
}
public interface LabelListener {
void onDismiss();
}
}

View file

@ -0,0 +1,70 @@
package net.mynero.wallet.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 com.google.android.material.bottomsheet.BottomSheetDialogFragment
import net.mynero.wallet.R
import net.mynero.wallet.model.WalletManager
import net.mynero.wallet.service.AddressService
import net.mynero.wallet.util.Helper.getClipBoardText
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
class EditAddressLabelBottomSheetDialog : BottomSheetDialogFragment() {
@JvmField
var listener: LabelListener? = null
@JvmField
var addressIndex = 0
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.bottom_sheet_dialog_edit_address_label, null)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val wallet = WalletManager.instance?.wallet
val addressService = AddressService.instance
val pasteButton = view.findViewById<ImageButton>(R.id.paste_password_imagebutton)
val labelEditText = view.findViewById<EditText>(R.id.wallet_password_edittext)
val saveLabelButton = view.findViewById<Button>(R.id.unlock_wallet_button)
var isDate = false
try {
wallet?.getSubaddressLabel(
addressIndex
)?.let {
SimpleDateFormat("yyyy-MM-dd-HH:mm:ss", Locale.US).parse(
it
)
isDate = true
}
} catch (ignored: ParseException) {
}
labelEditText.setText(if (isDate) null else wallet?.getSubaddressLabel(addressIndex))
pasteButton.setOnClickListener { labelEditText.setText(getClipBoardText(view.context)) }
saveLabelButton.setOnClickListener {
val label = labelEditText.text.toString()
if (addressService?.latestAddressIndex == addressIndex) {
addressService.freshSubaddress()
}
wallet?.setSubaddressLabel(addressIndex, label)
wallet?.store()
if (listener != null) {
listener?.onDismiss()
}
dismiss()
}
}
interface LabelListener {
fun onDismiss()
}
}

View file

@ -1,144 +0,0 @@
package net.mynero.wallet.fragment.dialog;
import android.content.Context;
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.ImageButton;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import net.mynero.wallet.R;
import net.mynero.wallet.data.Node;
import net.mynero.wallet.util.Helper;
import org.json.JSONException;
import org.json.JSONObject;
public class EditNodeBottomSheetDialog extends BottomSheetDialogFragment {
public EditNodeListener listener = null;
public Node node = null;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.edit_node_bottom_sheet_dialog, null);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Button deleteNodeButton = view.findViewById(R.id.delete_node_button);
Button doneEditingButton = view.findViewById(R.id.done_editing_button);
EditText addressEditText = view.findViewById(R.id.address_edittext);
EditText portEditText = view.findViewById(R.id.node_port_edittext);
EditText nodeNameEditText = view.findViewById(R.id.node_name_edittext);
EditText usernameEditText = view.findViewById(R.id.username_edittext);
EditText passwordEditText = view.findViewById(R.id.password_edittext);
ImageButton pastePasswordImageButton = view.findViewById(R.id.paste_password_imagebutton);
if (node == null) return;
addressEditText.setText(node.getHost());
portEditText.setText("" + node.getRpcPort());
nodeNameEditText.setText(node.getName());
usernameEditText.setText(node.getUsername());
if (!node.getPassword().isEmpty()) {
passwordEditText.setText(node.getPassword());
passwordEditText.setVisibility(View.VISIBLE);
pastePasswordImageButton.setVisibility(View.VISIBLE);
}
usernameEditText.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) {
if (editable.toString().isEmpty()) {
passwordEditText.setText(null);
passwordEditText.setVisibility(View.GONE);
pastePasswordImageButton.setVisibility(View.GONE);
} else {
passwordEditText.setVisibility(View.VISIBLE);
pastePasswordImageButton.setVisibility(View.VISIBLE);
}
}
});
addPasteListener(view, addressEditText, R.id.paste_address_imagebutton);
addPasteListener(view, usernameEditText, R.id.paste_username_imagebutton);
addPasteListener(view, passwordEditText, R.id.paste_password_imagebutton);
deleteNodeButton.setOnClickListener(view1 -> {
listener.onNodeDeleted(node);
dismiss();
});
doneEditingButton.setOnClickListener(view1 -> {
String nodeAddr = addressEditText.getText().toString();
String portString = portEditText.getText().toString();
String nodeName = nodeNameEditText.getText().toString();
String user = usernameEditText.getText().toString();
String pass = passwordEditText.getText().toString();
if (nodeName.isEmpty()) {
Toast.makeText(getContext(), "Enter node name", Toast.LENGTH_SHORT).show();
return;
} else if (nodeAddr.isEmpty() || portString.isEmpty()) {
Toast.makeText(getContext(), "Enter node address", Toast.LENGTH_SHORT).show();
return;
} else if (!user.isEmpty() && pass.isEmpty()) {
Toast.makeText(getContext(), "Enter password", Toast.LENGTH_SHORT).show();
return;
}
JSONObject jsonObject = new JSONObject();
try {
if (!user.isEmpty()) {
jsonObject.put("username", user);
jsonObject.put("password", pass);
}
if (nodeAddr.contains(":") && !nodeAddr.startsWith("[") && !nodeAddr.endsWith("]"))
nodeAddr = "[" + nodeAddr + "]";
jsonObject.put("host", nodeAddr);
jsonObject.put("rpcPort", Integer.parseInt(portString));
jsonObject.put("network", "mainnet");
jsonObject.put("name", nodeName);
listener.onNodeEdited(node, Node.fromJson(jsonObject));
dismiss();
} catch (JSONException e) {
throw new RuntimeException(e);
}
});
}
private void addPasteListener(View root, EditText editText, int layoutId) {
ImageButton pasteImageButton = root.findViewById(layoutId);
pasteImageButton.setOnClickListener(view1 -> {
Context ctx = getContext();
if (ctx != null) {
editText.setText(Helper.getClipBoardText(ctx));
}
});
}
public interface EditNodeListener {
void onNodeDeleted(Node node);
void onNodeEdited(Node oldNode, Node newNode);
}
}

View file

@ -0,0 +1,126 @@
package net.mynero.wallet.fragment.dialog
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.ImageButton
import android.widget.Toast
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import net.mynero.wallet.R
import net.mynero.wallet.data.Node
import net.mynero.wallet.data.Node.Companion.fromJson
import net.mynero.wallet.util.Helper.getClipBoardText
import org.json.JSONException
import org.json.JSONObject
class EditNodeBottomSheetDialog : BottomSheetDialogFragment() {
@JvmField
var listener: EditNodeListener? = null
@JvmField
var node: Node? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.edit_node_bottom_sheet_dialog, null)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val deleteNodeButton = view.findViewById<Button>(R.id.delete_node_button)
val doneEditingButton = view.findViewById<Button>(R.id.done_editing_button)
val addressEditText = view.findViewById<EditText>(R.id.address_edittext)
val portEditText = view.findViewById<EditText>(R.id.node_port_edittext)
val nodeNameEditText = view.findViewById<EditText>(R.id.node_name_edittext)
val usernameEditText = view.findViewById<EditText>(R.id.username_edittext)
val passwordEditText = view.findViewById<EditText>(R.id.password_edittext)
val pastePasswordImageButton =
view.findViewById<ImageButton>(R.id.paste_password_imagebutton)
if (node == null) return
addressEditText.setText(node?.host)
portEditText.setText("${node?.rpcPort}")
nodeNameEditText.setText(node?.name)
usernameEditText.setText(node?.username)
if (node?.password?.isNotEmpty() == true) {
passwordEditText.setText(node?.password)
passwordEditText.visibility = View.VISIBLE
pastePasswordImageButton.visibility = View.VISIBLE
}
usernameEditText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
if (editable.toString().isEmpty()) {
passwordEditText.text = null
passwordEditText.visibility = View.GONE
pastePasswordImageButton.visibility = View.GONE
} else {
passwordEditText.visibility = View.VISIBLE
pastePasswordImageButton.visibility = View.VISIBLE
}
}
})
addPasteListener(view, addressEditText, R.id.paste_address_imagebutton)
addPasteListener(view, usernameEditText, R.id.paste_username_imagebutton)
addPasteListener(view, passwordEditText, R.id.paste_password_imagebutton)
deleteNodeButton.setOnClickListener {
listener?.onNodeDeleted(node)
dismiss()
}
doneEditingButton.setOnClickListener {
var nodeAddr = addressEditText.text.toString()
val portString = portEditText.text.toString()
val nodeName = nodeNameEditText.text.toString()
val user = usernameEditText.text.toString()
val pass = passwordEditText.text.toString()
if (nodeName.isEmpty()) {
Toast.makeText(context, "Enter node name", Toast.LENGTH_SHORT).show()
return@setOnClickListener
} else if (nodeAddr.isEmpty() || portString.isEmpty()) {
Toast.makeText(context, "Enter node address", Toast.LENGTH_SHORT).show()
return@setOnClickListener
} else if (user.isNotEmpty() && pass.isEmpty()) {
Toast.makeText(context, "Enter password", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
val jsonObject = JSONObject()
try {
if (user.isNotEmpty()) {
jsonObject.put("username", user)
jsonObject.put("password", pass)
}
if (nodeAddr.contains(":") && !nodeAddr.startsWith("[") && !nodeAddr.endsWith("]")) nodeAddr =
"[$nodeAddr]"
jsonObject.put("host", nodeAddr)
jsonObject.put("rpcPort", portString.toInt())
jsonObject.put("network", "mainnet")
jsonObject.put("name", nodeName)
listener?.onNodeEdited(node, fromJson(jsonObject))
dismiss()
} catch (e: JSONException) {
throw RuntimeException(e)
}
}
}
private fun addPasteListener(root: View, editText: EditText, layoutId: Int) {
val pasteImageButton = root.findViewById<ImageButton>(layoutId)
pasteImageButton.setOnClickListener {
val ctx = context
if (ctx != null) {
editText.setText(getClipBoardText(ctx))
}
}
}
interface EditNodeListener {
fun onNodeDeleted(node: Node?)
fun onNodeEdited(oldNode: Node?, newNode: Node?)
}
}

View file

@ -1,132 +0,0 @@
package net.mynero.wallet.fragment.dialog;
import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import net.mynero.wallet.R;
import net.mynero.wallet.adapter.NodeSelectionAdapter;
import net.mynero.wallet.data.DefaultNodes;
import net.mynero.wallet.data.Node;
import net.mynero.wallet.model.WalletManager;
import net.mynero.wallet.service.PrefService;
import net.mynero.wallet.util.Constants;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public class NodeSelectionBottomSheetDialog extends BottomSheetDialogFragment implements NodeSelectionAdapter.NodeSelectionAdapterListener {
public NodeSelectionDialogListener listener = null;
private NodeSelectionAdapter adapter = null;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.node_selection_bottom_sheet_dialog, null);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ArrayList<Node> nodes = new ArrayList<>();
adapter = new NodeSelectionAdapter(this);
RecyclerView recyclerView = view.findViewById(R.id.node_selection_recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setAdapter(adapter);
Button addNodeButton = view.findViewById(R.id.add_node_button);
addNodeButton.setOnClickListener(view1 -> {
if (listener != null) {
listener.onClickedAddNode();
}
dismiss();
});
String nodesArray = PrefService.getInstance().getString(Constants.PREF_CUSTOM_NODES, "[]");
JSONArray jsonArray = null;
try {
jsonArray = new JSONArray(nodesArray);
} catch (JSONException e) {
throw new RuntimeException(e);
}
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject nodeJsonObject = null;
try {
nodeJsonObject = jsonArray.getJSONObject(i);
if (nodeJsonObject != null) {
Node node = Node.fromJson(nodeJsonObject);
if (node != null) {
nodes.add(node);
}
}
} catch (JSONException e) {
// if stored node is old string format, try parse and upgrade, if fail then remove
try {
String nodeString = jsonArray.getString(i);
Node node = Node.fromString(nodeString);
if (node != null) {
nodes.add(node);
jsonArray.put(i, node.toJson());
} else {
jsonArray.remove(i);
}
} catch (JSONException ex) {
throw new RuntimeException(ex);
}
}
}
PrefService.getInstance().edit().putString(Constants.PREF_CUSTOM_NODES, jsonArray.toString()).apply();
for (DefaultNodes defaultNode : DefaultNodes.values()) {
nodes.add(Node.fromJson(defaultNode.getJson()));
}
adapter.submitList(nodes);
}
@Override
public void onSelectNode(Node node) {
Activity activity = getActivity();
if (activity != null) {
activity.runOnUiThread(() -> {
Toast.makeText(activity, getString(R.string.node_selected), Toast.LENGTH_SHORT).show();
});
}
PrefService.getInstance().edit().putString(Constants.PREF_NODE_2, node.toJson().toString()).apply();
WalletManager.getInstance().setDaemon(node);
adapter.updateSelectedNode();
listener.onNodeSelected();
}
@Override
public boolean onSelectEditNode(Node node) {
if (listener != null) {
listener.onClickedEditNode(node);
}
dismiss();
return true;
}
public interface NodeSelectionDialogListener {
void onNodeSelected();
void onClickedEditNode(Node node);
void onClickedAddNode();
}
}

View file

@ -0,0 +1,120 @@
package net.mynero.wallet.fragment.dialog
import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import net.mynero.wallet.R
import net.mynero.wallet.adapter.NodeSelectionAdapter
import net.mynero.wallet.adapter.NodeSelectionAdapter.NodeSelectionAdapterListener
import net.mynero.wallet.data.DefaultNodes
import net.mynero.wallet.data.Node
import net.mynero.wallet.data.Node.Companion.fromJson
import net.mynero.wallet.data.Node.Companion.fromString
import net.mynero.wallet.model.WalletManager
import net.mynero.wallet.service.PrefService
import net.mynero.wallet.util.Constants
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
class NodeSelectionBottomSheetDialog : BottomSheetDialogFragment(), NodeSelectionAdapterListener {
@JvmField
var listener: NodeSelectionDialogListener? = null
private var adapter: NodeSelectionAdapter? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.node_selection_bottom_sheet_dialog, null)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val nodes = ArrayList<Node>()
adapter = NodeSelectionAdapter(this)
val recyclerView = view.findViewById<RecyclerView>(R.id.node_selection_recyclerview)
recyclerView.layoutManager = LinearLayoutManager(activity)
recyclerView.adapter = adapter
val addNodeButton = view.findViewById<Button>(R.id.add_node_button)
addNodeButton.setOnClickListener {
if (listener != null) {
listener?.onClickedAddNode()
}
dismiss()
}
val nodesArray = PrefService.instance?.getString(Constants.PREF_CUSTOM_NODES, "[]")
val jsonArray: JSONArray = try {
JSONArray(nodesArray)
} catch (e: JSONException) {
return
}
for (i in 0 until jsonArray.length()) {
var nodeJsonObject: JSONObject?
try {
nodeJsonObject = jsonArray.getJSONObject(i)
if (nodeJsonObject != null) {
val node = fromJson(nodeJsonObject)
if (node != null) {
nodes.add(node)
}
}
} catch (e: JSONException) {
// if stored node is old string format, try parse and upgrade, if fail then remove
try {
val nodeString = jsonArray.getString(i)
val node = fromString(nodeString)
if (node != null) {
nodes.add(node)
jsonArray.put(i, node.toJson())
} else {
jsonArray.remove(i)
}
} catch (ex: JSONException) {
throw RuntimeException(ex)
}
}
}
PrefService.instance?.edit()?.putString(Constants.PREF_CUSTOM_NODES, jsonArray.toString())?.apply()
for (defaultNode in DefaultNodes.values()) {
fromJson(defaultNode.json)?.let { nodes.add(it) }
}
adapter?.submitList(nodes)
}
override fun onSelectNode(node: Node?) {
val activity: Activity? = activity
activity?.runOnUiThread {
Toast.makeText(
activity,
getString(R.string.node_selected),
Toast.LENGTH_SHORT
).show()
}
PrefService.instance?.edit()?.putString(Constants.PREF_NODE_2, node?.toJson().toString())?.apply()
WalletManager.instance?.setDaemon(node)
adapter?.updateSelectedNode()
listener?.onNodeSelected()
}
override fun onSelectEditNode(node: Node?): Boolean {
if (listener != null) {
listener?.onClickedEditNode(node)
}
dismiss()
return true
}
interface NodeSelectionDialogListener {
fun onNodeSelected()
fun onClickedEditNode(node: Node?)
fun onClickedAddNode()
}
}

View file

@ -1,67 +0,0 @@
package net.mynero.wallet.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 androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import net.mynero.wallet.R;
import net.mynero.wallet.model.WalletManager;
import net.mynero.wallet.util.Constants;
import net.mynero.wallet.util.Helper;
import java.io.File;
public class PasswordBottomSheetDialog extends BottomSheetDialogFragment {
public PasswordListener listener = null;
public boolean cancelable = false;
@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);
setCancelable(cancelable);
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) {
return WalletManager.getInstance().verifyWalletPasswordOnly(walletFile.getAbsolutePath() + ".keys", password);
}
public interface PasswordListener {
void onPasswordSuccess(String password);
void onPasswordFail();
}
}

View file

@ -0,0 +1,66 @@
package net.mynero.wallet.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 com.google.android.material.bottomsheet.BottomSheetDialogFragment
import net.mynero.wallet.R
import net.mynero.wallet.model.WalletManager
import net.mynero.wallet.util.Constants
import net.mynero.wallet.util.Helper.getClipBoardText
import java.io.File
class PasswordBottomSheetDialog : BottomSheetDialogFragment() {
@JvmField
var listener: PasswordListener? = null
@JvmField
var cancelable = false
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.password_bottom_sheet_dialog, null)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
isCancelable = cancelable
val walletFile = File(activity?.applicationInfo?.dataDir, Constants.WALLET_NAME)
val pastePasswordImageButton =
view.findViewById<ImageButton>(R.id.paste_password_imagebutton)
val passwordEditText = view.findViewById<EditText>(R.id.wallet_password_edittext)
val unlockWalletButton = view.findViewById<Button>(R.id.unlock_wallet_button)
pastePasswordImageButton.setOnClickListener {
passwordEditText.setText(
getClipBoardText(view.context)
)
}
unlockWalletButton.setOnClickListener {
val password = passwordEditText.text.toString()
val success = checkPassword(walletFile, password)
if (success) {
listener?.onPasswordSuccess(password)
dismiss()
} else {
listener?.onPasswordFail()
}
}
}
private fun checkPassword(walletFile: File, password: String): Boolean {
return WalletManager.instance?.verifyWalletPasswordOnly(
walletFile.absolutePath + ".keys",
password
) == true
}
interface PasswordListener {
fun onPasswordSuccess(password: String)
fun onPasswordFail()
}
}

View file

@ -1,356 +0,0 @@
package net.mynero.wallet.fragment.dialog;
import android.app.Activity;
import android.content.Context;
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.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.zxing.client.android.Intents;
import com.journeyapps.barcodescanner.ScanContract;
import com.journeyapps.barcodescanner.ScanOptions;
import net.mynero.wallet.MoneroApplication;
import net.mynero.wallet.R;
import net.mynero.wallet.model.CoinsInfo;
import net.mynero.wallet.model.PendingTransaction;
import net.mynero.wallet.model.Wallet;
import net.mynero.wallet.service.BalanceService;
import net.mynero.wallet.service.TxService;
import net.mynero.wallet.service.UTXOService;
import net.mynero.wallet.util.Constants;
import net.mynero.wallet.util.Helper;
import net.mynero.wallet.util.UriData;
import java.util.ArrayList;
import java.util.List;
public class SendBottomSheetDialog extends BottomSheetDialogFragment {
private final MutableLiveData<Boolean> _sendingMax = new MutableLiveData<>(false);
private final MutableLiveData<PendingTransaction> _pendingTransaction = new MutableLiveData<>(null);
public ArrayList<String> selectedUtxos = new ArrayList<>();
public LiveData<Boolean> sendingMax = _sendingMax;
public LiveData<PendingTransaction> pendingTransaction = _pendingTransaction;
public UriData uriData = null; private final ActivityResultLauncher<String> cameraPermissionsLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(),
granted -> {
if (granted) {
onScan();
} else {
Toast.makeText(getActivity(), getString(R.string.no_camera_permission), Toast.LENGTH_SHORT).show();
}
});
public boolean isChurning = false;
public Listener listener = null;
public PendingTransaction.Priority priority;
private EditText addressEditText;
private EditText amountEditText;
private final ActivityResultLauncher<ScanOptions> barcodeLauncher = registerForActivityResult(new ScanContract(),
result -> {
if (result.getContents() != null) {
pasteAddress(result.getContents());
}
});
private TextView sendAllTextView;
private TextView feeTextView;
private TextView addressTextView;
private TextView amountTextView;
private TextView feeRadioGroupLabelTextView;
private TextView selectedUtxosValueTextView;
private Button createButton;
private Button sendButton;
private Button sendMaxButton;
private ImageButton pasteAddressImageButton;
private ImageButton scanAddressImageButton;
private RadioGroup feeRadioGroup;
private TextView donateTextView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.send_bottom_sheet_dialog, null);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
pasteAddressImageButton = view.findViewById(R.id.paste_address_imagebutton);
scanAddressImageButton = view.findViewById(R.id.scan_address_imagebutton);
sendMaxButton = view.findViewById(R.id.send_max_button);
addressEditText = view.findViewById(R.id.address_edittext);
amountEditText = view.findViewById(R.id.amount_edittext);
sendButton = view.findViewById(R.id.send_tx_button);
createButton = view.findViewById(R.id.create_tx_button);
sendAllTextView = view.findViewById(R.id.sending_all_textview);
feeTextView = view.findViewById(R.id.fee_textview);
addressTextView = view.findViewById(R.id.address_pending_textview);
amountTextView = view.findViewById(R.id.amount_pending_textview);
feeRadioGroup = view.findViewById(R.id.tx_fee_radiogroup);
feeRadioGroupLabelTextView = view.findViewById(R.id.tx_fee_radiogroup_label_textview);
selectedUtxosValueTextView = view.findViewById(R.id.selected_utxos_value_textview);
donateTextView = view.findViewById(R.id.donate_label_textview);
donateTextView.setOnClickListener(view1 -> addressEditText.setText(Constants.DONATE_ADDRESS));
if (uriData != null) {
addressEditText.setText(uriData.address);
if (uriData.hasAmount()) {
amountEditText.setText(uriData.getAmount());
}
}
if (!selectedUtxos.isEmpty()) {
long selectedValue = 0;
for (CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
if (selectedUtxos.contains(coinsInfo.keyImage)) {
selectedValue += coinsInfo.amount;
}
}
String valueString = Wallet.getDisplayAmount(selectedValue);
selectedUtxosValueTextView.setVisibility(View.VISIBLE);
if (isChurning) {
_sendingMax.postValue(true);
sendMaxButton.setEnabled(false);
selectedUtxosValueTextView.setText(getResources().getString(R.string.selected_utxos_value_churning, valueString));
} else {
selectedUtxosValueTextView.setText(getResources().getString(R.string.selected_utxos_value, valueString));
}
} else {
selectedUtxosValueTextView.setVisibility(View.GONE);
}
bindObservers();
bindListeners();
}
private void bindObservers() {
BalanceService.instance.balanceInfo.observe(getViewLifecycleOwner(), balanceInfo -> {
createButton.setEnabled(balanceInfo.getRawUnlocked() != 0);
if (!isChurning) {
sendMaxButton.setEnabled(balanceInfo.getRawUnlocked() != 0);
}
});
sendingMax.observe(getViewLifecycleOwner(), sendingMax -> {
if (pendingTransaction.getValue() == null) {
if (sendingMax) {
amountEditText.setVisibility(View.INVISIBLE);
sendAllTextView.setVisibility(View.VISIBLE);
sendMaxButton.setText(getText(R.string.undo));
} else {
amountEditText.setVisibility(View.VISIBLE);
sendAllTextView.setVisibility(View.GONE);
sendMaxButton.setText(getText(R.string.send_max));
}
}
});
pendingTransaction.observe(getViewLifecycleOwner(), pendingTx -> {
showConfirmationLayout(pendingTx != null);
if (pendingTx != null) {
String address = addressEditText.getText().toString();
addressTextView.setText(getString(R.string.tx_address_text, address));
amountTextView.setText(getString(R.string.tx_amount_text, Helper.getDisplayAmount(pendingTx.getAmount())));
feeTextView.setText(getString(R.string.tx_fee_text, Helper.getDisplayAmount(pendingTx.getFee())));
}
});
}
private void bindListeners() {
feeRadioGroup.check(R.id.low_fee_radiobutton);
priority = PendingTransaction.Priority.Priority_Low;
feeRadioGroup.setOnCheckedChangeListener((radioGroup, i) -> {
if (i == R.id.low_fee_radiobutton) {
priority = PendingTransaction.Priority.Priority_Low;
} else if (i == R.id.med_fee_radiobutton) {
priority = PendingTransaction.Priority.Priority_Medium;
} else if (i == R.id.high_fee_radiobutton) {
priority = PendingTransaction.Priority.Priority_High;
}
});
pasteAddressImageButton.setOnClickListener(view1 -> {
Context ctx = getContext();
if (ctx != null) {
String clipboard = Helper.getClipBoardText(ctx);
if (clipboard != null) {
pasteAddress(clipboard);
}
}
});
scanAddressImageButton.setOnClickListener(view1 -> {
onScan();
});
sendMaxButton.setOnClickListener(view1 -> {
boolean currentValue = sendingMax.getValue() != null ? sendingMax.getValue() : false;
_sendingMax.postValue(!currentValue);
});
createButton.setOnClickListener(view1 -> {
FragmentActivity activity = getActivity();
if (activity != null) {
boolean sendAll = sendingMax.getValue() != null ? sendingMax.getValue() : false;
String address = addressEditText.getText().toString().trim();
String amount = amountEditText.getText().toString().trim();
boolean validAddress = Wallet.isAddressValid(address);
if (validAddress && (!amount.isEmpty() || sendAll)) {
long amountRaw = Wallet.getAmountFromString(amount);
long balance = BalanceService.instance.getUnlockedBalanceRaw();
if ((amountRaw >= balance || amountRaw <= 0) && !sendAll) {
Toast.makeText(activity, getString(R.string.send_amount_invalid), Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(activity, getString(R.string.creating_tx), Toast.LENGTH_SHORT).show();
createButton.setEnabled(false);
createTx(address, amount, sendAll, priority);
} else if (!validAddress) {
Toast.makeText(activity, getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(activity, getString(R.string.send_amount_empty), Toast.LENGTH_SHORT).show();
}
}
});
sendButton.setOnClickListener(view1 -> {
PendingTransaction pendingTx = pendingTransaction.getValue();
if (pendingTx != null) {
Toast.makeText(getActivity(), getString(R.string.sending_tx), Toast.LENGTH_SHORT).show();
sendButton.setEnabled(false);
sendTx(pendingTx);
}
});
}
private void onScan() {
if (Helper.getCameraPermission(getActivity(), cameraPermissionsLauncher)) {
ScanOptions options = new ScanOptions();
options.setBeepEnabled(false);
options.setOrientationLocked(true);
options.setDesiredBarcodeFormats(List.of(Intents.Scan.QR_CODE_MODE));
options.addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN);
barcodeLauncher.launch(options);
}
}
private void sendTx(PendingTransaction pendingTx) {
Activity activity = getActivity();
if (activity != null) {
((MoneroApplication) activity.getApplication()).getExecutor().execute(() -> {
boolean success = TxService.instance.sendTx(pendingTx);
activity.runOnUiThread(() -> {
if (success) {
Toast.makeText(activity, getString(R.string.sent_tx), Toast.LENGTH_SHORT).show();
if (listener != null) {
listener.onSentTransaction();
}
dismiss();
} else {
sendButton.setEnabled(true);
Toast.makeText(activity, getString(R.string.error_sending_tx), Toast.LENGTH_SHORT).show();
}
});
});
}
}
private void createTx(String address, String amount, boolean sendAll, PendingTransaction.Priority feePriority) {
Activity activity = getActivity();
if (activity != null) {
((MoneroApplication) activity.getApplication()).getExecutor().execute(() -> {
try {
PendingTransaction pendingTx = TxService.instance.createTx(address, amount, sendAll, feePriority, selectedUtxos);
if (pendingTx != null && pendingTx.getStatus() == PendingTransaction.Status.Status_Ok) {
_pendingTransaction.postValue(pendingTx);
} else {
activity.runOnUiThread(() -> {
createButton.setEnabled(true);
if (pendingTx != null) {
Toast.makeText(activity, getString(R.string.error_creating_tx, pendingTx.getErrorString()), Toast.LENGTH_SHORT).show();
}
});
}
} catch (Exception e) {
activity.runOnUiThread(() -> {
createButton.setEnabled(true);
Toast.makeText(activity, e.getMessage(), Toast.LENGTH_SHORT).show();
});
}
});
}
}
private void showConfirmationLayout(boolean show) {
if (show) {
sendButton.setVisibility(View.VISIBLE);
addressEditText.setVisibility(View.GONE);
amountEditText.setVisibility(View.GONE);
sendAllTextView.setVisibility(View.GONE);
createButton.setVisibility(View.GONE);
sendMaxButton.setVisibility(View.GONE);
pasteAddressImageButton.setVisibility(View.GONE);
scanAddressImageButton.setVisibility(View.GONE);
feeTextView.setVisibility(View.VISIBLE);
addressTextView.setVisibility(View.VISIBLE);
amountTextView.setVisibility(View.VISIBLE);
selectedUtxosValueTextView.setVisibility(View.GONE);
feeRadioGroup.setVisibility(View.GONE);
feeRadioGroupLabelTextView.setVisibility(View.GONE);
donateTextView.setVisibility(View.GONE);
} else {
sendButton.setVisibility(View.GONE);
addressEditText.setVisibility(View.VISIBLE);
amountEditText.setVisibility(Boolean.TRUE.equals(sendingMax.getValue()) ? View.GONE : View.VISIBLE);
sendAllTextView.setVisibility(Boolean.TRUE.equals(sendingMax.getValue()) ? View.VISIBLE : View.GONE);
createButton.setVisibility(View.VISIBLE);
sendMaxButton.setVisibility(View.VISIBLE);
pasteAddressImageButton.setVisibility(View.VISIBLE);
scanAddressImageButton.setVisibility(View.VISIBLE);
feeTextView.setVisibility(View.GONE);
addressTextView.setVisibility(View.GONE);
amountTextView.setVisibility(View.GONE);
if (!selectedUtxos.isEmpty()) {
selectedUtxosValueTextView.setVisibility(View.VISIBLE);
}
feeRadioGroup.setVisibility(View.VISIBLE);
feeRadioGroupLabelTextView.setVisibility(View.VISIBLE);
donateTextView.setVisibility(View.VISIBLE);
}
}
private void pasteAddress(String address) {
UriData uriData = UriData.parse(address);
if (uriData != null) {
addressEditText.setText(uriData.address);
if (uriData.hasAmount()) {
amountEditText.setText(uriData.getAmount());
}
} else {
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
}
}
public interface Listener {
void onSentTransaction();
}
}

View file

@ -0,0 +1,392 @@
package net.mynero.wallet.fragment.dialog
import android.app.Activity
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.RadioGroup
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.zxing.client.android.Intents
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanIntentResult
import com.journeyapps.barcodescanner.ScanOptions
import net.mynero.wallet.MoneroApplication
import net.mynero.wallet.R
import net.mynero.wallet.model.BalanceInfo
import net.mynero.wallet.model.PendingTransaction
import net.mynero.wallet.model.Wallet
import net.mynero.wallet.model.Wallet.Companion.getAmountFromString
import net.mynero.wallet.model.Wallet.Companion.isAddressValid
import net.mynero.wallet.service.BalanceService
import net.mynero.wallet.service.TxService
import net.mynero.wallet.service.UTXOService
import net.mynero.wallet.util.Constants
import net.mynero.wallet.util.Helper
import net.mynero.wallet.util.Helper.getCameraPermission
import net.mynero.wallet.util.Helper.getClipBoardText
import net.mynero.wallet.util.UriData
import net.mynero.wallet.util.UriData.Companion.parse
class SendBottomSheetDialog : BottomSheetDialogFragment() {
private val _sendingMax = MutableLiveData(false)
private val _pendingTransaction = MutableLiveData<PendingTransaction?>(null)
@JvmField
var selectedUtxos = ArrayList<String>()
private var sendingMax: LiveData<Boolean?> = _sendingMax
private var pendingTransaction: LiveData<PendingTransaction?> = _pendingTransaction
@JvmField
var uriData: UriData? = null
private val cameraPermissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted: Boolean ->
if (granted) {
onScan()
} else {
Toast.makeText(activity, getString(R.string.no_camera_permission), Toast.LENGTH_SHORT)
.show()
}
}
@JvmField
var isChurning = false
@JvmField
var listener: Listener? = null
var priority: PendingTransaction.Priority = PendingTransaction.Priority.Priority_Low
private var addressEditText: EditText? = null
private var amountEditText: EditText? = null
private val barcodeLauncher = registerForActivityResult(
ScanContract()
) { result: ScanIntentResult ->
if (result.contents != null) {
pasteAddress(result.contents)
}
}
private var sendAllTextView: TextView? = null
private var feeTextView: TextView? = null
private var addressTextView: TextView? = null
private var amountTextView: TextView? = null
private var feeRadioGroupLabelTextView: TextView? = null
private var selectedUtxosValueTextView: TextView? = null
private var createButton: Button? = null
private var sendButton: Button? = null
private var sendMaxButton: Button? = null
private var pasteAddressImageButton: ImageButton? = null
private var scanAddressImageButton: ImageButton? = null
private var feeRadioGroup: RadioGroup? = null
private var donateTextView: TextView? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.send_bottom_sheet_dialog, null)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
pasteAddressImageButton = view.findViewById(R.id.paste_address_imagebutton)
scanAddressImageButton = view.findViewById(R.id.scan_address_imagebutton)
sendMaxButton = view.findViewById(R.id.send_max_button)
addressEditText = view.findViewById(R.id.address_edittext)
amountEditText = view.findViewById(R.id.amount_edittext)
sendButton = view.findViewById(R.id.send_tx_button)
createButton = view.findViewById(R.id.create_tx_button)
sendAllTextView = view.findViewById(R.id.sending_all_textview)
feeTextView = view.findViewById(R.id.fee_textview)
addressTextView = view.findViewById(R.id.address_pending_textview)
amountTextView = view.findViewById(R.id.amount_pending_textview)
feeRadioGroup = view.findViewById(R.id.tx_fee_radiogroup)
feeRadioGroupLabelTextView = view.findViewById(R.id.tx_fee_radiogroup_label_textview)
selectedUtxosValueTextView = view.findViewById(R.id.selected_utxos_value_textview)
donateTextView = view.findViewById(R.id.donate_label_textview)
donateTextView?.setOnClickListener {
addressEditText?.setText(
Constants.DONATE_ADDRESS
)
}
if (uriData != null) {
addressEditText?.setText(uriData?.address)
if (uriData?.hasAmount() == true) {
amountEditText?.setText(uriData?.amount)
}
}
if (selectedUtxos.isNotEmpty()) {
var selectedValue: Long = 0
val utxos = UTXOService.instance?.getUtxos() ?: return
for (coinsInfo in utxos) {
if (selectedUtxos.contains(coinsInfo.keyImage)) {
selectedValue += coinsInfo.amount
}
}
val valueString = Wallet.getDisplayAmount(selectedValue)
selectedUtxosValueTextView?.visibility = View.VISIBLE
if (isChurning) {
_sendingMax.postValue(true)
sendMaxButton?.isEnabled = false
selectedUtxosValueTextView?.text = resources.getString(
R.string.selected_utxos_value_churning,
valueString
)
} else {
selectedUtxosValueTextView?.text = resources.getString(
R.string.selected_utxos_value,
valueString
)
}
} else {
selectedUtxosValueTextView?.visibility = View.GONE
}
bindObservers()
bindListeners()
}
private fun bindObservers() {
BalanceService.instance?.balanceInfo?.observe(viewLifecycleOwner) { balanceInfo ->
createButton?.isEnabled = balanceInfo?.rawUnlocked != 0L
if (!isChurning) {
sendMaxButton?.isEnabled = balanceInfo?.rawUnlocked != 0L
}
}
sendingMax.observe(viewLifecycleOwner) { sendingMax: Boolean? ->
if (pendingTransaction.value == null) {
if (sendingMax == true) {
amountEditText?.visibility = View.INVISIBLE
sendAllTextView?.visibility = View.VISIBLE
sendMaxButton?.text = getText(R.string.undo)
} else {
amountEditText?.visibility = View.VISIBLE
sendAllTextView?.visibility = View.GONE
sendMaxButton?.text = getText(R.string.send_max)
}
}
}
pendingTransaction.observe(viewLifecycleOwner) { pendingTx: PendingTransaction? ->
showConfirmationLayout(pendingTx != null)
if (pendingTx != null) {
val address = addressEditText?.text.toString()
addressTextView?.text = getString(R.string.tx_address_text, address)
amountTextView?.text = getString(
R.string.tx_amount_text,
Helper.getDisplayAmount(pendingTx.getAmount())
)
feeTextView?.text =
getString(R.string.tx_fee_text, Helper.getDisplayAmount(pendingTx.getFee()))
}
}
}
private fun bindListeners() {
feeRadioGroup?.check(R.id.low_fee_radiobutton)
feeRadioGroup?.setOnCheckedChangeListener { _: RadioGroup?, i: Int ->
when (i) {
R.id.low_fee_radiobutton -> priority = PendingTransaction.Priority.Priority_Low
R.id.med_fee_radiobutton -> priority = PendingTransaction.Priority.Priority_Medium
R.id.high_fee_radiobutton -> priority = PendingTransaction.Priority.Priority_High
}
}
pasteAddressImageButton?.setOnClickListener {
val ctx = context
if (ctx != null) {
val clipboard = getClipBoardText(ctx)
clipboard?.let { pasteAddress(it) }
}
}
scanAddressImageButton?.setOnClickListener { onScan() }
sendMaxButton?.setOnClickListener {
val currentValue = if (sendingMax.value != null) sendingMax.value else false
_sendingMax.postValue(currentValue == false)
}
createButton?.setOnClickListener {
val activity = activity
if (activity != null) {
val sendAll = sendingMax.value ?: false
val address = addressEditText?.text.toString().trim { it <= ' ' }
val amount = amountEditText?.text.toString().trim { it <= ' ' }
val validAddress = isAddressValid(address)
if (validAddress && (amount.isNotEmpty() || sendAll)) {
val amountRaw = getAmountFromString(amount)
val balance = BalanceService.instance?.unlockedBalanceRaw ?: 0
if ((amountRaw >= balance || amountRaw <= 0) && !sendAll) {
Toast.makeText(
activity,
getString(R.string.send_amount_invalid),
Toast.LENGTH_SHORT
).show()
return@setOnClickListener
}
Toast.makeText(activity, getString(R.string.creating_tx), Toast.LENGTH_SHORT)
.show()
createButton?.isEnabled = false
createTx(address, amount, sendAll, priority)
} else if (!validAddress) {
Toast.makeText(
activity,
getString(R.string.send_address_invalid),
Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
activity,
getString(R.string.send_amount_empty),
Toast.LENGTH_SHORT
).show()
}
}
}
sendButton?.setOnClickListener {
val pendingTx = pendingTransaction.value
if (pendingTx != null) {
Toast.makeText(activity, getString(R.string.sending_tx), Toast.LENGTH_SHORT).show()
sendButton?.isEnabled = false
sendTx(pendingTx)
}
}
}
private fun onScan() {
if (activity?.let { getCameraPermission(it, cameraPermissionsLauncher) } == true) {
val options = ScanOptions()
options.setBeepEnabled(false)
options.setOrientationLocked(true)
options.setDesiredBarcodeFormats(listOf(Intents.Scan.QR_CODE_MODE))
options.addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN)
barcodeLauncher.launch(options)
}
}
private fun sendTx(pendingTx: PendingTransaction) {
val activity: Activity? = activity
if (activity != null) {
(activity.application as MoneroApplication).executor?.execute {
val success = TxService.instance?.sendTx(pendingTx)
activity.runOnUiThread(Runnable {
if (success == true) {
Toast.makeText(activity, getString(R.string.sent_tx), Toast.LENGTH_SHORT)
.show()
if (listener != null) {
listener?.onSentTransaction()
}
dismiss()
} else {
sendButton?.isEnabled = true
Toast.makeText(
activity,
getString(R.string.error_sending_tx),
Toast.LENGTH_SHORT
).show()
}
})
}
}
}
private fun createTx(
address: String,
amount: String,
sendAll: Boolean,
feePriority: PendingTransaction.Priority
) {
val activity: Activity? = activity
if (activity != null) {
(activity.application as MoneroApplication).executor?.execute {
try {
val pendingTx = TxService.instance?.createTx(
address,
amount,
sendAll,
feePriority,
selectedUtxos
)
if (pendingTx != null && pendingTx.status === PendingTransaction.Status.Status_Ok) {
_pendingTransaction.postValue(pendingTx)
} else {
activity.runOnUiThread(Runnable {
createButton?.isEnabled = true
if (pendingTx != null) {
Toast.makeText(
activity,
getString(
R.string.error_creating_tx,
pendingTx.getErrorString()
),
Toast.LENGTH_SHORT
).show()
}
})
}
} catch (e: Exception) {
activity.runOnUiThread(Runnable {
createButton?.isEnabled = true
Toast.makeText(activity, e.message, Toast.LENGTH_SHORT).show()
})
}
}
}
}
private fun showConfirmationLayout(show: Boolean) {
if (show) {
sendButton?.visibility = View.VISIBLE
addressEditText?.visibility = View.GONE
amountEditText?.visibility = View.GONE
sendAllTextView?.visibility = View.GONE
createButton?.visibility = View.GONE
sendMaxButton?.visibility = View.GONE
pasteAddressImageButton?.visibility = View.GONE
scanAddressImageButton?.visibility = View.GONE
feeTextView?.visibility = View.VISIBLE
addressTextView?.visibility = View.VISIBLE
amountTextView?.visibility = View.VISIBLE
selectedUtxosValueTextView?.visibility = View.GONE
feeRadioGroup?.visibility = View.GONE
feeRadioGroupLabelTextView?.visibility = View.GONE
donateTextView?.visibility = View.GONE
} else {
sendButton?.visibility = View.GONE
addressEditText?.visibility = View.VISIBLE
amountEditText?.visibility =
if (java.lang.Boolean.TRUE == sendingMax.value) View.GONE else View.VISIBLE
sendAllTextView?.visibility =
if (java.lang.Boolean.TRUE == sendingMax.value) View.VISIBLE else View.GONE
createButton?.visibility = View.VISIBLE
sendMaxButton?.visibility = View.VISIBLE
pasteAddressImageButton?.visibility = View.VISIBLE
scanAddressImageButton?.visibility = View.VISIBLE
feeTextView?.visibility = View.GONE
addressTextView?.visibility = View.GONE
amountTextView?.visibility = View.GONE
if (selectedUtxos.isNotEmpty()) {
selectedUtxosValueTextView?.visibility = View.VISIBLE
}
feeRadioGroup?.visibility = View.VISIBLE
feeRadioGroupLabelTextView?.visibility = View.VISIBLE
donateTextView?.visibility = View.VISIBLE
}
}
private fun pasteAddress(address: String) {
val uriData = parse(address)
if (uriData != null) {
addressEditText?.setText(uriData.address)
if (uriData.hasAmount()) {
amountEditText?.setText(uriData.amount)
}
} else {
Toast.makeText(activity, getString(R.string.send_address_invalid), Toast.LENGTH_SHORT)
.show()
}
}
interface Listener {
fun onSentTransaction()
}
}

View file

@ -1,53 +0,0 @@
package net.mynero.wallet.fragment.dialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import net.mynero.wallet.R;
import net.mynero.wallet.model.Wallet;
import net.mynero.wallet.model.WalletManager;
import net.mynero.wallet.service.PrefService;
import net.mynero.wallet.util.Constants;
import net.mynero.wallet.util.Helper;
public class WalletKeysBottomSheetDialog extends BottomSheetDialogFragment {
public String password = "";
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.wallet_keys_bottom_sheet_dialog, null);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ImageButton copyViewKeyImageButton = view.findViewById(R.id.copy_viewkey_imagebutton);
TextView informationTextView = view.findViewById(R.id.information_textview); // seed
TextView viewKeyTextView = view.findViewById(R.id.viewkey_textview);
TextView restoreHeightTextView = view.findViewById(R.id.restore_height_textview);
Wallet wallet = WalletManager.getInstance().getWallet();
String seed = wallet.getSeed("");
boolean usesOffset = PrefService.getInstance().getBoolean(Constants.PREF_USES_OFFSET, false);
if (usesOffset) {
seed = wallet.getSeed(password);
view.findViewById(R.id.wallet_seed_offset_textview).setVisibility(View.VISIBLE);
}
String privateViewKey = wallet.getSecretViewKey();
informationTextView.setText(seed);
viewKeyTextView.setText(privateViewKey);
restoreHeightTextView.setText(wallet.getRestoreHeight() + "");
copyViewKeyImageButton.setOnClickListener(view1 -> Helper.clipBoardCopy(getContext(), "private view-key", privateViewKey));
}
}

View file

@ -0,0 +1,50 @@
package net.mynero.wallet.fragment.dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import net.mynero.wallet.R
import net.mynero.wallet.model.WalletManager
import net.mynero.wallet.service.PrefService
import net.mynero.wallet.util.Constants
import net.mynero.wallet.util.Helper.clipBoardCopy
class WalletKeysBottomSheetDialog : BottomSheetDialogFragment() {
@JvmField
var password = ""
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.wallet_keys_bottom_sheet_dialog, null)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val copyViewKeyImageButton = view.findViewById<ImageButton>(R.id.copy_viewkey_imagebutton)
val informationTextView = view.findViewById<TextView>(R.id.information_textview) // seed
val viewKeyTextView = view.findViewById<TextView>(R.id.viewkey_textview)
val restoreHeightTextView = view.findViewById<TextView>(R.id.restore_height_textview)
val wallet = WalletManager.instance!!.wallet
var seed = wallet!!.getSeed("")
val usesOffset = PrefService.instance!!.getBoolean(Constants.PREF_USES_OFFSET, false)
if (usesOffset) {
seed = wallet.getSeed(password)
view.findViewById<View>(R.id.wallet_seed_offset_textview).visibility = View.VISIBLE
}
val privateViewKey = wallet.getSecretViewKey()
informationTextView.text = seed
viewKeyTextView.text = privateViewKey
restoreHeightTextView.text = "${wallet.getRestoreHeight()}"
copyViewKeyImageButton.setOnClickListener {
clipBoardCopy(
context, "private view-key", privateViewKey
)
}
}
}

View file

@ -1,204 +0,0 @@
package net.mynero.wallet.fragment.home;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import net.mynero.wallet.MainActivity;
import net.mynero.wallet.R;
import net.mynero.wallet.adapter.TransactionInfoAdapter;
import net.mynero.wallet.model.TransactionInfo;
import net.mynero.wallet.model.Wallet;
import net.mynero.wallet.model.WalletManager;
import net.mynero.wallet.service.BalanceService;
import net.mynero.wallet.service.BlockchainService;
import net.mynero.wallet.service.HistoryService;
import net.mynero.wallet.service.PrefService;
import net.mynero.wallet.util.Constants;
import java.util.Collections;
public class HomeFragment extends Fragment implements TransactionInfoAdapter.TxInfoAdapterListener {
long startHeight = 0;
private HomeViewModel mViewModel;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_home, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
MainActivity mainActivity = (MainActivity) getActivity();
mViewModel = new ViewModelProvider(this).get(HomeViewModel.class);
bindObservers(view);
bindListeners(view);
mainActivity.restartEvents.observe(getViewLifecycleOwner(), o -> {
bindObservers(view);
bindListeners(view);
});
}
private void bindListeners(View view) {
ImageView settingsImageView = view.findViewById(R.id.settings_imageview);
Button sendButton = view.findViewById(R.id.send_button);
Button receiveButton = view.findViewById(R.id.receive_button);
settingsImageView.setOnClickListener(view12 -> {
navigate(HomeFragmentDirections.navToSettings());
});
sendButton.setOnClickListener(view1 -> {
navigate(HomeFragmentDirections.navToSend());
});
receiveButton.setOnClickListener(view1 -> {
navigate(HomeFragmentDirections.navToReceive());
});
}
private void bindObservers(View view) {
RecyclerView txHistoryRecyclerView = view.findViewById(R.id.transaction_history_recyclerview);
TextView unlockedBalanceTextView = view.findViewById(R.id.balance_unlocked_textview);
TextView lockedBalanceTextView = view.findViewById(R.id.balance_locked_textview);
BalanceService balanceService = BalanceService.instance;
HistoryService historyService = HistoryService.getInstance();
BlockchainService blockchainService = BlockchainService.instance;
if (balanceService != null) {
balanceService.balanceInfo.observe(getViewLifecycleOwner(), balanceInfo -> {
if (balanceInfo != null) {
unlockedBalanceTextView.setText(balanceInfo.getUnlockedDisplay());
if (balanceInfo.getLockedDisplay().equals(Constants.STREET_MODE_BALANCE) || balanceInfo.isLockedBalanceZero()) {
lockedBalanceTextView.setVisibility(View.INVISIBLE);
} else {
lockedBalanceTextView.setText(getString(R.string.wallet_locked_balance_text, balanceInfo.getLockedDisplay()));
lockedBalanceTextView.setVisibility(View.VISIBLE);
}
}
});
}
ProgressBar progressBar = view.findViewById(R.id.sync_progress_bar);
TextView progressBarText = view.findViewById(R.id.sync_progress_text);
if (blockchainService != null) {
blockchainService.height.observe(getViewLifecycleOwner(), height -> {
Wallet wallet = WalletManager.getInstance().getWallet();
if (!wallet.isSynchronized()) {
if (startHeight == 0 && height != 1) {
startHeight = height;
}
long daemonHeight = blockchainService.getDaemonHeight();
long n = daemonHeight - height;
int x = 100 - Math.round(100f * n / (1f * daemonHeight - startHeight));
progressBar.setIndeterminate(height <= 1 || daemonHeight <= 0);
if (height > 1 && daemonHeight > 1) {
progressBar.setProgress(x);
progressBarText.setVisibility(View.VISIBLE);
progressBarText.setText("Syncing... " + n + " blocks remaining");
} else {
progressBarText.setVisibility(View.GONE);
}
} else {
progressBar.setVisibility(View.INVISIBLE);
progressBarText.setVisibility(View.GONE);
}
});
}
TransactionInfoAdapter adapter = new TransactionInfoAdapter(this);
txHistoryRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
txHistoryRecyclerView.setAdapter(adapter);
if (historyService != null) {
historyService.history.observe(getViewLifecycleOwner(), history -> {
if (history.isEmpty()) {
// DISPLAYING EMPTY WALLET HISTORY
Wallet wallet = WalletManager.getInstance().getWallet();
int textResId, botImgResId = 0;
if (wallet != null && wallet.isSynchronized()) {
textResId = R.string.no_history_nget_some_monero_in_here;
botImgResId = R.drawable.xmrchan_empty; // img for synchronized
} else {
textResId = R.string.no_history_loading;
botImgResId = R.drawable.xmrchan_loading; // img for loading
}
txHistoryRecyclerView.setVisibility(View.GONE);
displayEmptyHistory(true, view, textResId, botImgResId);
} else {
// POPULATED WALLET HISTORY
Collections.sort(history);
if (history.size() > 100) {
adapter.submitList(history.subList(0, 99));
} else {
adapter.submitList(history);
}
txHistoryRecyclerView.setVisibility(View.VISIBLE);
displayEmptyHistory(false, view, R.string.no_history_nget_some_monero_in_here, R.drawable.xmrchan_loading);
}
});
}
}
@Override
public void onClickTransaction(TransactionInfo txInfo) {
NavDirections directions = HomeFragmentDirections.navToTransaction(txInfo);
navigate(directions);
}
private void navigate(NavDirections destination) {
FragmentActivity activity = getActivity();
if (activity != null) {
FragmentManager fm = activity.getSupportFragmentManager();
NavHostFragment navHostFragment =
(NavHostFragment) fm.findFragmentById(R.id.nav_host_fragment);
if (navHostFragment != null) {
navHostFragment.getNavController().navigate(destination);
}
}
}
private void displayEmptyHistory(boolean display, View view, int textResId, int botImgResId) {
TextView mnrjTextView = view.findViewById(R.id.monerochan_empty_tx_textview);
TextView textView = view.findViewById(R.id.empty_tx_textview);
ImageView botImageView = view.findViewById(R.id.monerochan_imageview);
view.findViewById(R.id.no_history_layout).setVisibility(display ? View.VISIBLE : View.GONE);
boolean displayMonerochan = PrefService.getInstance().getBoolean(Constants.PREF_MONEROCHAN, true);
if (displayMonerochan) {
botImageView.setVisibility(View.VISIBLE);
mnrjTextView.setVisibility(View.VISIBLE);
textView.setVisibility(View.GONE);
} else {
botImageView.setVisibility(View.GONE);
mnrjTextView.setVisibility(View.GONE);
textView.setVisibility(View.VISIBLE);
}
botImageView.setImageResource(botImgResId);
mnrjTextView.setText(textResId);
textView.setText(textResId);
}
}

View file

@ -0,0 +1,183 @@
package net.mynero.wallet.fragment.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavDirections
import androidx.navigation.fragment.NavHostFragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import net.mynero.wallet.MainActivity
import net.mynero.wallet.R
import net.mynero.wallet.adapter.TransactionInfoAdapter
import net.mynero.wallet.adapter.TransactionInfoAdapter.TxInfoAdapterListener
import net.mynero.wallet.model.TransactionInfo
import net.mynero.wallet.model.WalletManager
import net.mynero.wallet.service.BalanceService
import net.mynero.wallet.service.BlockchainService
import net.mynero.wallet.service.HistoryService
import net.mynero.wallet.service.PrefService
import net.mynero.wallet.util.Constants
import kotlin.math.roundToInt
class HomeFragment : Fragment(), TxInfoAdapterListener {
private var startHeight: Long = 0
private var mViewModel: HomeViewModel? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val mainActivity = activity as MainActivity?
mViewModel = ViewModelProvider(this)[HomeViewModel::class.java]
bindObservers(view)
bindListeners(view)
mainActivity?.restartEvents?.observe(viewLifecycleOwner) {
bindObservers(view)
bindListeners(view)
}
}
private fun bindListeners(view: View) {
val settingsImageView = view.findViewById<ImageView>(R.id.settings_imageview)
val sendButton = view.findViewById<Button>(R.id.send_button)
val receiveButton = view.findViewById<Button>(R.id.receive_button)
settingsImageView.setOnClickListener { navigate(HomeFragmentDirections.navToSettings()) }
sendButton.setOnClickListener { navigate(HomeFragmentDirections.navToSend()) }
receiveButton.setOnClickListener { navigate(HomeFragmentDirections.navToReceive()) }
}
private fun bindObservers(view: View) {
val txHistoryRecyclerView =
view.findViewById<RecyclerView>(R.id.transaction_history_recyclerview)
val unlockedBalanceTextView = view.findViewById<TextView>(R.id.balance_unlocked_textview)
val lockedBalanceTextView = view.findViewById<TextView>(R.id.balance_locked_textview)
val balanceService = BalanceService.instance
val historyService = HistoryService.instance
val blockchainService = BlockchainService.instance
balanceService?.balanceInfo?.observe(viewLifecycleOwner) { balanceInfo ->
if (balanceInfo != null) {
unlockedBalanceTextView.text = balanceInfo.unlockedDisplay
if (balanceInfo.lockedDisplay == Constants.STREET_MODE_BALANCE || balanceInfo.isLockedBalanceZero) {
lockedBalanceTextView.visibility = View.INVISIBLE
} else {
lockedBalanceTextView.text = getString(
R.string.wallet_locked_balance_text,
balanceInfo.lockedDisplay
)
lockedBalanceTextView.visibility = View.VISIBLE
}
}
}
val progressBar = view.findViewById<ProgressBar>(R.id.sync_progress_bar)
val progressBarText = view.findViewById<TextView>(R.id.sync_progress_text)
blockchainService?.height?.observe(viewLifecycleOwner) { height: Long ->
val wallet = WalletManager.instance?.wallet
if (wallet?.isSynchronized == false) {
if (startHeight == 0L && height != 1L) {
startHeight = height
}
val daemonHeight = blockchainService.daemonHeight
val n = daemonHeight - height
val x = Math.round(100 - (100f * n / (1f * daemonHeight - startHeight)))
progressBar.isIndeterminate = height <= 1 || daemonHeight <= 0
if (height > 1 && daemonHeight > 1) {
progressBar.progress = x
progressBarText.visibility = View.VISIBLE
progressBarText.text = "Syncing... $n blocks remaining"
} else {
progressBarText.visibility = View.GONE
}
} else {
progressBar.visibility = View.INVISIBLE
progressBarText.visibility = View.GONE
}
}
val adapter = TransactionInfoAdapter(this)
txHistoryRecyclerView.layoutManager = LinearLayoutManager(activity)
txHistoryRecyclerView.adapter = adapter
historyService?.history?.observe(viewLifecycleOwner) { history: List<TransactionInfo> ->
if (history.isEmpty()) {
// DISPLAYING EMPTY WALLET HISTORY
val wallet = WalletManager.instance?.wallet
val textResId: Int
val botImgResId = if (wallet != null && wallet.isSynchronized) {
textResId = R.string.no_history_nget_some_monero_in_here
R.drawable.xmrchan_empty // img for synchronized
} else {
textResId = R.string.no_history_loading
R.drawable.xmrchan_loading // img for loading
}
txHistoryRecyclerView.visibility = View.GONE
displayEmptyHistory(true, view, textResId, botImgResId)
} else {
// POPULATED WALLET HISTORY
history.sorted()
if (history.size > 100) {
adapter.submitList(history.subList(0, 99))
} else {
adapter.submitList(history)
}
txHistoryRecyclerView.visibility = View.VISIBLE
displayEmptyHistory(
false,
view,
R.string.no_history_nget_some_monero_in_here,
R.drawable.xmrchan_loading
)
}
}
}
override fun onClickTransaction(txInfo: TransactionInfo?) {
val directions: NavDirections = HomeFragmentDirections.navToTransaction(txInfo)
navigate(directions)
}
private fun navigate(destination: NavDirections) {
val activity = activity
if (activity != null) {
val fm = activity.supportFragmentManager
val navHostFragment = fm.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
navHostFragment?.navController?.navigate(destination)
}
}
private fun displayEmptyHistory(
display: Boolean,
view: View,
textResId: Int,
botImgResId: Int
) {
val mnrjTextView = view.findViewById<TextView>(R.id.monerochan_empty_tx_textview)
val textView = view.findViewById<TextView>(R.id.empty_tx_textview)
val botImageView = view.findViewById<ImageView>(R.id.monerochan_imageview)
view.findViewById<View>(R.id.no_history_layout).visibility =
if (display) View.VISIBLE else View.GONE
val displayMonerochan = PrefService.instance?.getBoolean(Constants.PREF_MONEROCHAN, true)
if (displayMonerochan == true) {
botImageView.visibility = View.VISIBLE
mnrjTextView.visibility = View.VISIBLE
textView.visibility = View.GONE
} else {
botImageView.visibility = View.GONE
mnrjTextView.visibility = View.GONE
textView.visibility = View.VISIBLE
}
botImageView.setImageResource(botImgResId)
mnrjTextView.setText(textResId)
textView.setText(textResId)
}
}

View file

@ -1,7 +0,0 @@
package net.mynero.wallet.fragment.home;
import androidx.lifecycle.ViewModel;
public class HomeViewModel extends ViewModel {
}

View file

@ -0,0 +1,5 @@
package net.mynero.wallet.fragment.home
import androidx.lifecycle.ViewModel
class HomeViewModel : ViewModel()

View file

@ -70,7 +70,7 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf
sendUtxosButton.setOnClickListener(view1 -> { sendUtxosButton.setOnClickListener(view1 -> {
ArrayList<String> selectedKeyImages = new ArrayList<>(); ArrayList<String> selectedKeyImages = new ArrayList<>();
for (CoinsInfo coinsInfo : adapter.selectedUtxos.values()) { for (CoinsInfo coinsInfo : adapter.selectedUtxos.values()) {
selectedKeyImages.add(coinsInfo.keyImage); selectedKeyImages.add(coinsInfo.getKeyImage());
} }
SendBottomSheetDialog sendDialog = new SendBottomSheetDialog(); SendBottomSheetDialog sendDialog = new SendBottomSheetDialog();
sendDialog.listener = this; sendDialog.listener = this;
@ -80,7 +80,7 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf
churnUtxosButton.setOnClickListener(view1 -> { churnUtxosButton.setOnClickListener(view1 -> {
ArrayList<String> selectedKeyImages = new ArrayList<>(); ArrayList<String> selectedKeyImages = new ArrayList<>();
for (CoinsInfo coinsInfo : adapter.selectedUtxos.values()) { for (CoinsInfo coinsInfo : adapter.selectedUtxos.values()) {
selectedKeyImages.add(coinsInfo.keyImage); selectedKeyImages.add(coinsInfo.getKeyImage());
} }
SendBottomSheetDialog sendDialog = new SendBottomSheetDialog(); SendBottomSheetDialog sendDialog = new SendBottomSheetDialog();
sendDialog.listener = this; sendDialog.listener = this;
@ -101,7 +101,7 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf
HashMap<String, CoinsInfo> filteredUtxos = new HashMap<>(); HashMap<String, CoinsInfo> filteredUtxos = new HashMap<>();
for (CoinsInfo coinsInfo : utxos) { for (CoinsInfo coinsInfo : utxos) {
if (!coinsInfo.isSpent()) { if (!coinsInfo.isSpent()) {
filteredUtxos.put(coinsInfo.pubKey, coinsInfo); filteredUtxos.put(coinsInfo.getPubKey(), coinsInfo);
} }
} }
if (filteredUtxos.isEmpty()) { if (filteredUtxos.isEmpty()) {

View file

@ -20,28 +20,15 @@ import android.os.Parcelable
import android.os.Parcelable.Creator import android.os.Parcelable.Creator
class CoinsInfo : Parcelable, Comparable<CoinsInfo> { class CoinsInfo : Parcelable, Comparable<CoinsInfo> {
@JvmField
var globalOutputIndex: Long var globalOutputIndex: Long
var isSpent = false var isSpent = false
@JvmField
var keyImage: String? = null var keyImage: String? = null
@JvmField
var amount: Long = 0 var amount: Long = 0
@JvmField
var hash: String? = null var hash: String? = null
@JvmField
var pubKey: String? = null var pubKey: String? = null
var isUnlocked = false var isUnlocked = false
@JvmField
var localOutputIndex: Long = 0 var localOutputIndex: Long = 0
var isFrozen = false var isFrozen = false
@JvmField
var address: String? = null var address: String? = null
constructor( constructor(
@ -68,7 +55,7 @@ class CoinsInfo : Parcelable, Comparable<CoinsInfo> {
this.address = address this.address = address
} }
protected constructor(`in`: Parcel) { private constructor(`in`: Parcel) {
globalOutputIndex = `in`.readLong() globalOutputIndex = `in`.readLong()
} }
@ -80,15 +67,15 @@ class CoinsInfo : Parcelable, Comparable<CoinsInfo> {
parcel.writeLong(globalOutputIndex) parcel.writeLong(globalOutputIndex)
} }
override fun compareTo(another: CoinsInfo): Int { override fun compareTo(other: CoinsInfo): Int {
val b1 = amount val b1 = amount
val b2 = another.amount val b2 = other.amount
return if (b1 > b2) { return if (b1 > b2) {
-1 -1
} else if (b1 < b2) { } else if (b1 < b2) {
1 1
} else { } else {
hash!!.compareTo(another.hash!!) other.hash?.let { hash?.compareTo(it) } ?: 0
} }
} }

View file

@ -15,11 +15,10 @@
*/ */
package net.mynero.wallet.model package net.mynero.wallet.model
enum class NetworkType(@JvmField val value: Int) { enum class NetworkType(val value: Int) {
NetworkType_Mainnet(0), NetworkType_Testnet(1), NetworkType_Stagenet(2); NetworkType_Mainnet(0), NetworkType_Testnet(1), NetworkType_Stagenet(2);
companion object { companion object {
@JvmStatic
fun fromInteger(n: Int): NetworkType? { fun fromInteger(n: Int): NetworkType? {
when (n) { when (n) {
0 -> return NetworkType_Mainnet 0 -> return NetworkType_Mainnet

View file

@ -38,7 +38,7 @@ class PendingTransaction internal constructor(var handle: Long) {
Status_Ok, Status_Error, Status_Critical Status_Ok, Status_Error, Status_Critical
} }
enum class Priority(@JvmField val value: Int) { enum class Priority(value: Int) {
Priority_Default(0), Priority_Low(1), Priority_Medium(2), Priority_High(3), Priority_Last(4); Priority_Default(0), Priority_Low(1), Priority_Medium(2), Priority_High(3), Priority_Last(4);
companion object { companion object {

View file

@ -19,6 +19,8 @@ package net.mynero.wallet.model;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import androidx.annotation.NonNull;
import net.mynero.wallet.data.Subaddress; import net.mynero.wallet.data.Subaddress;
import java.util.List; import java.util.List;
@ -100,7 +102,7 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
addressIndex = in.readInt(); addressIndex = in.readInt();
confirmations = in.readLong(); confirmations = in.readLong();
subaddressLabel = in.readString(); subaddressLabel = in.readString();
transfers = in.readArrayList(Transfer.class.getClassLoader()); in.readList(transfers, Transfer.class.getClassLoader());
txKey = in.readString(); txKey = in.readString();
notes = in.readString(); notes = in.readString();
address = in.readString(); address = in.readString();
@ -117,6 +119,8 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
return subaddressLabel; return subaddressLabel;
} }
@Override
@NonNull
public String toString() { public String toString() {
return direction + "@" + blockheight + " " + amount; return direction + "@" + blockheight + " " + amount;
} }
@ -171,13 +175,11 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
} }
public static Direction fromInteger(int n) { public static Direction fromInteger(int n) {
switch (n) { return switch (n) {
case 0: case 0 -> Direction_In;
return Direction_In; case 1 -> Direction_Out;
case 1: default -> null;
return Direction_Out; };
}
return null;
} }
public int getValue() { public int getValue() {

View file

@ -1,3 +1,3 @@
package net.mynero.wallet.model package net.mynero.wallet.model
class TransactionOutput(@JvmField val destination: String, @JvmField val amount: Long) class TransactionOutput(val destination: String, val amount: Long)

View file

@ -17,7 +17,6 @@ package net.mynero.wallet.model
import android.util.Pair import android.util.Pair
import net.mynero.wallet.data.Subaddress import net.mynero.wallet.data.Subaddress
import net.mynero.wallet.model.NetworkType.Companion.fromInteger
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -129,7 +128,7 @@ class Wallet {
external fun getPath(): String? external fun getPath(): String?
val networkType: NetworkType? val networkType: NetworkType?
get() = fromInteger(nettype()) get() = NetworkType.fromInteger(nettype())
external fun nettype(): Int external fun nettype(): Int
@ -255,7 +254,7 @@ class Wallet {
destinations: List<Pair<String, Long>>, destinations: List<Pair<String, Long>>,
priority: PendingTransaction.Priority priority: PendingTransaction.Priority
): Long { ): Long {
val _priority = priority.value val _priority = priority.ordinal
return estimateTransactionFee(destinations, _priority) return estimateTransactionFee(destinations, _priority)
} }
@ -270,7 +269,7 @@ class Wallet {
keyImages: ArrayList<String> keyImages: ArrayList<String>
): PendingTransaction? { ): PendingTransaction? {
disposePendingTransaction() disposePendingTransaction()
val _priority = priority.value val _priority = priority.ordinal
val txHandle = createSweepTransaction(dstAddr, "", 0, _priority, accountIndex, keyImages) val txHandle = createSweepTransaction(dstAddr, "", 0, _priority, accountIndex, keyImages)
pendingTransaction = PendingTransaction(txHandle) pendingTransaction = PendingTransaction(txHandle)
return pendingTransaction return pendingTransaction
@ -282,7 +281,7 @@ class Wallet {
keyImages: ArrayList<String> keyImages: ArrayList<String>
): PendingTransaction? { ): PendingTransaction? {
disposePendingTransaction() disposePendingTransaction()
val _priority = priority.value val _priority = priority.ordinal
val destinations = ArrayList<String>() val destinations = ArrayList<String>()
val amounts = LongArray(outputs.size) val amounts = LongArray(outputs.size)
for (i in outputs.indices) { for (i in outputs.indices) {

View file

@ -23,7 +23,6 @@ import java.util.Calendar
import java.util.Locale import java.util.Locale
class WalletManager { class WalletManager {
@JvmField
val networkType = NetworkType.NetworkType_Mainnet val networkType = NetworkType.NetworkType_Mainnet
var wallet: Wallet? = null var wallet: Wallet? = null
private set private set
@ -209,7 +208,7 @@ class WalletManager {
subaddressLookahead: String subaddressLookahead: String
): Long ): Long
external fun closeJ(wallet: Wallet?): Boolean private external fun closeJ(wallet: Wallet?): Boolean
fun close(wallet: Wallet): Boolean { fun close(wallet: Wallet): Boolean {
unmanageWallet(wallet) unmanageWallet(wallet)
val closed = closeJ(wallet) val closed = closeJ(wallet)
@ -225,28 +224,28 @@ class WalletManager {
return walletExists(aFile.absolutePath) return walletExists(aFile.absolutePath)
} }
external fun walletExists(path: String?): Boolean private external fun walletExists(path: String?): Boolean
external fun verifyWalletPassword( external fun verifyWalletPassword(
keys_file_name: String?, keysFileName: String?,
password: String?, password: String?,
watch_only: Boolean watchOnly: Boolean
): Boolean ): Boolean
fun verifyWalletPasswordOnly(keys_file_name: String, password: String): Boolean { fun verifyWalletPasswordOnly(keysFileName: String, password: String): Boolean {
return queryWalletDeviceJ(keys_file_name, password) >= 0 return queryWalletDeviceJ(keysFileName, password) >= 0
} }
fun queryWalletDevice(keys_file_name: String, password: String): Wallet.Device { fun queryWalletDevice(keysFileName: String, password: String): Wallet.Device {
val device = queryWalletDeviceJ(keys_file_name, password) val device = queryWalletDeviceJ(keysFileName, password)
return Wallet.Device.values()[device + 1] // mapping is monero+1=android return Wallet.Device.values()[device + 1] // mapping is monero+1=android
} }
private external fun queryWalletDeviceJ(keys_file_name: String, password: String): Int private external fun queryWalletDeviceJ(keysFileName: String, password: String): Int
fun findWallets(path: File): List<WalletInfo> { fun findWallets(path: File): List<WalletInfo> {
val wallets: MutableList<WalletInfo> = ArrayList() val wallets: MutableList<WalletInfo> = ArrayList()
Timber.d("Scanning: %s", path.absolutePath) Timber.d("Scanning: %s", path.absolutePath)
val found = val found =
path.listFiles { dir, filename -> filename.endsWith(".keys") } ?: return emptyList() path.listFiles { _, filename -> filename.endsWith(".keys") } ?: return emptyList()
for (i in found.indices) { for (i in found.indices) {
val filename = found[i].name val filename = found[i].name
val f = File( val f = File(
@ -291,8 +290,8 @@ class WalletManager {
external fun startMining( external fun startMining(
address: String?, address: String?,
background_mining: Boolean, backgroundMining: Boolean,
ignore_battery: Boolean ignoreBattery: Boolean
): Boolean ): Boolean
external fun stopMining(): Boolean external fun stopMining(): Boolean
@ -320,7 +319,6 @@ class WalletManager {
companion object { companion object {
//TODO: maybe put these in an enum like in monero core - but why? //TODO: maybe put these in an enum like in monero core - but why?
@JvmField
var LOGLEVEL_SILENT = -1 var LOGLEVEL_SILENT = -1
var LOGLEVEL_WARN = 0 var LOGLEVEL_WARN = 0
var LOGLEVEL_INFO = 1 var LOGLEVEL_INFO = 1
@ -349,7 +347,6 @@ class WalletManager {
NetworkType.NetworkType_Testnet -> "9A-" NetworkType.NetworkType_Testnet -> "9A-"
NetworkType.NetworkType_Mainnet -> "4-" NetworkType.NetworkType_Mainnet -> "4-"
NetworkType.NetworkType_Stagenet -> "5-" NetworkType.NetworkType_Stagenet -> "5-"
else -> throw IllegalStateException("Unsupported Network: $networkType")
} }
} }

View file

@ -8,10 +8,7 @@ import net.mynero.wallet.model.WalletManager
class BlockchainService(thread: MoneroHandlerThread) : ServiceBase(thread) { class BlockchainService(thread: MoneroHandlerThread) : ServiceBase(thread) {
private val _currentHeight = MutableLiveData(0L) private val _currentHeight = MutableLiveData(0L)
private val _connectionStatus = MutableLiveData(ConnectionStatus.ConnectionStatus_Disconnected) private val _connectionStatus = MutableLiveData(ConnectionStatus.ConnectionStatus_Disconnected)
@JvmField
var height: LiveData<Long> = _currentHeight var height: LiveData<Long> = _currentHeight
@JvmField @JvmField
var connectionStatus: LiveData<ConnectionStatus> = _connectionStatus var connectionStatus: LiveData<ConnectionStatus> = _connectionStatus
var daemonHeight: Long = 0 var daemonHeight: Long = 0
@ -22,7 +19,7 @@ class BlockchainService(thread: MoneroHandlerThread) : ServiceBase(thread) {
lastDaemonHeightUpdateTimeMs = t lastDaemonHeightUpdateTimeMs = t
} else { } else {
if (t - lastDaemonHeightUpdateTimeMs > 120000) { if (t - lastDaemonHeightUpdateTimeMs > 120000) {
field = WalletManager.instance!!.wallet!!.getDaemonBlockChainHeight() field = WalletManager.instance?.wallet?.getDaemonBlockChainHeight() ?: return
lastDaemonHeightUpdateTimeMs = t lastDaemonHeightUpdateTimeMs = t
} }
} }
@ -37,8 +34,8 @@ class BlockchainService(thread: MoneroHandlerThread) : ServiceBase(thread) {
_currentHeight.postValue(currentHeight) _currentHeight.postValue(currentHeight)
} }
val currentHeight: Long private val currentHeight: Long
get() = WalletManager.instance!!.wallet!!.getBlockChainHeight() get() = WalletManager.instance?.wallet?.getBlockChainHeight() ?: -1
fun setConnectionStatus(status: ConnectionStatus) { fun setConnectionStatus(status: ConnectionStatus) {
_connectionStatus.postValue(status) _connectionStatus.postValue(status)

View file

@ -20,7 +20,7 @@ class HistoryService(thread: MoneroHandlerThread) : ServiceBase(thread) {
} }
private fun getHistory(): List<TransactionInfo> { private fun getHistory(): List<TransactionInfo> {
return WalletManager.instance!!.wallet!!.history!!.all return WalletManager.instance?.wallet?.history?.all ?: emptyList()
} }
companion object { companion object {

View file

@ -32,10 +32,10 @@ import java.security.SecureRandom
* used to create handler classes. Note that start() must still be called. * used to create handler classes. Note that start() must still be called.
* The started Thread has a stck size of STACK_SIZE (=5MB) * The started Thread has a stck size of STACK_SIZE (=5MB)
*/ */
class MoneroHandlerThread(name: String?, val listener: Listener?, wallet: Wallet) : class MoneroHandlerThread(name: String, val listener: Listener?, wallet: Wallet) :
Thread(null, null, name, THREAD_STACK_SIZE), WalletListener { Thread(null, null, name, THREAD_STACK_SIZE), WalletListener {
private val wallet: Wallet private val wallet: Wallet
var triesLeft = 5 private var triesLeft = 5
init { init {
this.wallet = wallet this.wallet = wallet

View file

@ -1,3 +1,3 @@
package net.mynero.wallet.service package net.mynero.wallet.service
open class ServiceBase(@JvmField val thread: MoneroHandlerThread?) open class ServiceBase(val thread: MoneroHandlerThread?)

View file

@ -55,7 +55,7 @@ class UTXOService(thread: MoneroHandlerThread?) : ServiceBase(thread) {
frozenCoins = frozenCoinsCopy frozenCoins = frozenCoinsCopy
saveFrozenCoins() saveFrozenCoins()
refreshUtxos() refreshUtxos()
BalanceService.instance!!.refreshBalance() BalanceService.instance?.refreshBalance()
} }
fun isCoinFrozen(coinsInfo: CoinsInfo): Boolean { fun isCoinFrozen(coinsInfo: CoinsInfo): Boolean {
@ -65,7 +65,7 @@ class UTXOService(thread: MoneroHandlerThread?) : ServiceBase(thread) {
@Throws(JSONException::class) @Throws(JSONException::class)
private fun loadFrozenCoins() { private fun loadFrozenCoins() {
val prefService = PrefService.instance val prefService = PrefService.instance
val frozenCoinsArrayString = prefService!!.getString(Constants.PREF_FROZEN_COINS, "[]") val frozenCoinsArrayString = prefService?.getString(Constants.PREF_FROZEN_COINS, "[]")
val frozenCoinsArray = JSONArray(frozenCoinsArrayString) val frozenCoinsArray = JSONArray(frozenCoinsArrayString)
for (i in 0 until frozenCoinsArray.length()) { for (i in 0 until frozenCoinsArray.length()) {
val pubKey = frozenCoinsArray.getString(i) val pubKey = frozenCoinsArray.getString(i)
@ -81,8 +81,7 @@ class UTXOService(thread: MoneroHandlerThread?) : ServiceBase(thread) {
for (pubKey in frozenCoinsCopy) { for (pubKey in frozenCoinsCopy) {
jsonArray.put(pubKey) jsonArray.put(pubKey)
} }
prefService!!.edit()!! prefService?.edit()?.putString(Constants.PREF_FROZEN_COINS, jsonArray.toString())?.apply()
.putString(Constants.PREF_FROZEN_COINS, jsonArray.toString()).apply()
} }
@Throws(Exception::class) @Throws(Exception::class)
@ -91,7 +90,7 @@ class UTXOService(thread: MoneroHandlerThread?) : ServiceBase(thread) {
sendAll: Boolean, sendAll: Boolean,
feePriority: PendingTransaction.Priority feePriority: PendingTransaction.Priority
): ArrayList<String> { ): ArrayList<String> {
val basicFeeEstimate = calculateBasicFee(amount, feePriority) val basicFeeEstimate = calculateBasicFee(amount, feePriority) ?: return arrayListOf()
val amountWithBasicFee = amount + basicFeeEstimate val amountWithBasicFee = amount + basicFeeEstimate
val selectedUtxos = ArrayList<String>() val selectedUtxos = ArrayList<String>()
val seenTxs = ArrayList<String>() val seenTxs = ArrayList<String>()
@ -125,7 +124,7 @@ class UTXOService(thread: MoneroHandlerThread?) : ServiceBase(thread) {
return selectedUtxos return selectedUtxos
} }
private fun calculateBasicFee(amount: Long, feePriority: PendingTransaction.Priority): Long { private fun calculateBasicFee(amount: Long, feePriority: PendingTransaction.Priority): Long? {
val destinations = ArrayList<Pair<String, Long>>() val destinations = ArrayList<Pair<String, Long>>()
destinations.add( destinations.add(
Pair( Pair(
@ -134,7 +133,7 @@ class UTXOService(thread: MoneroHandlerThread?) : ServiceBase(thread) {
) )
) )
// destination string doesn't actually matter here, so i'm using the donation address. amount also technically doesn't matter // destination string doesn't actually matter here, so i'm using the donation address. amount also technically doesn't matter
return WalletManager.instance!!.wallet!!.estimateTransactionFee(destinations, feePriority) return WalletManager.instance?.wallet?.estimateTransactionFee(destinations, feePriority)
} }
companion object { companion object {

View file

@ -19,7 +19,6 @@ object Constants {
const val NAV_ARG_TXINFO = "nav_arg_txinfo" const val NAV_ARG_TXINFO = "nav_arg_txinfo"
const val STREET_MODE_BALANCE = "#.############" const val STREET_MODE_BALANCE = "#.############"
@JvmField
val DONATION_ADDRESSES = arrayOf( val DONATION_ADDRESSES = arrayOf(
"87BqQYkugEzh6Tuyotm2uc3DzJzKM6MuZaC161e6u1TsQxxPmXVPHpdNRyK47JY4d1hhbe25YVz4e9vTXCLDxvHkRXEAeBC", // primary Mysu Donation address "87BqQYkugEzh6Tuyotm2uc3DzJzKM6MuZaC161e6u1TsQxxPmXVPHpdNRyK47JY4d1hhbe25YVz4e9vTXCLDxvHkRXEAeBC", // primary Mysu Donation address
"89QoPxs4cQSGbJrJddwzV3Ca7s2gVYHE1Xd1hGZafuVJVyNKt2LCQhxUdBF57PemxQiX3dmGUZLRRAzfeYyh9pq3GiWsDVo", // second Mysu Donation address "89QoPxs4cQSGbJrJddwzV3Ca7s2gVYHE1Xd1hGZafuVJVyNKt2LCQhxUdBF57PemxQiX3dmGUZLRRAzfeYyh9pq3GiWsDVo", // second Mysu Donation address