Skip to content

Commit

Permalink
Merge 6ee6ceb into 380f820
Browse files Browse the repository at this point in the history
  • Loading branch information
mpatc committed Sep 26, 2020
2 parents 380f820 + 6ee6ceb commit 2774b92
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package org.electroncash.electroncash3

import android.content.ClipboardManager
import android.content.Intent
import android.text.Editable
import android.text.TextWatcher
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.viewModels
import androidx.lifecycle.ViewModel
import com.google.zxing.integration.android.IntentIntegrator
import kotlinx.android.synthetic.main.load.*


val libTransaction by lazy { libMod("transaction") }


// This provides a dialog to allow users to input a string, which is then broadcast
// on the bitcoin cash network. Strings are not validated,
// but broadcast_transaction should throw error which is toasted.
// Valid transaction quickly show up in transactions.

class ColdLoadDialog : AlertDialogFragment() {

class Model : ViewModel() {}

val model: Model by viewModels()

override fun onBuildDialog(builder: AlertDialog.Builder) {
builder.setTitle(R.string.load_transaction)
.setView(R.layout.load)
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.qr_code, null)
.setPositiveButton(R.string.send, null)
}

override fun onShowDialog() {
super.onShowDialog()
etTransaction.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) { updateUI() }
})
updateUI()

dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { onOK() }
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { scanQR(this) }
btnPaste.setOnClickListener {
val clipdata = getSystemService(ClipboardManager::class).primaryClip
if (clipdata != null && clipdata.getItemCount() > 0) {
val cliptext = clipdata.getItemAt(0)
etTransaction.setText(cliptext.text)
}
}
}

private fun updateUI() {
val currenttext = etTransaction.text
//checks if text is blank. further validations can be added here
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = currenttext.isNotBlank()
}

// Receives the result of a QR scan.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
if (result != null && result.contents != null) {
etTransaction.setText(result.contents)
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}

fun onOK() {
val tx = libTransaction.callAttr("Transaction", etTransaction.text.toString())
if (!daemonModel.isConnected()) {
throw ToastException(R.string.not_connected)
}
val result = daemonModel.network.callAttr("broadcast_transaction", tx)
try {
checkBroadcastResult(result)
toast(R.string.the_string, Toast.LENGTH_LONG)
dismiss()
} catch (e: ToastException) { e.show() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,10 @@ class MainActivity : AppCompatActivity(R.layout.main) {
}
R.id.menuChangePassword -> showDialog(this, PasswordChangeDialog())
R.id.menuShowSeed-> { showDialog(this, SeedPasswordDialog()) }
R.id.menuExportSigned-> { showDialog(this, SendDialog().apply {
arguments = Bundle().apply {putBoolean("unbroadcasted", true)}
}) }
R.id.menuLoadSigned-> { showDialog(this, ColdLoadDialog()) }
R.id.menuRename -> showDialog(this, WalletRenameDialog().apply {
arguments = Bundle().apply { putString("walletName", daemonModel.walletName) }
})
Expand Down
71 changes: 44 additions & 27 deletions android/app/src/main/java/org/electroncash/electroncash3/Send.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class SendDialog : AlertDialogFragment() {
}
val model: Model by viewModels()

val unbroadcasted by lazy {
arguments?.getBoolean("unbroadcasted", false) ?: false
}

init {
if (daemonModel.wallet!!.callAttr("is_watching_only").toBoolean()) {
throw ToastException(R.string.this_wallet_is)
Expand All @@ -44,22 +48,29 @@ class SendDialog : AlertDialogFragment() {
}

override fun onBuildDialog(builder: AlertDialog.Builder) {
builder.setTitle(R.string.send)
.setView(R.layout.send)
if (!unbroadcasted) {
builder.setTitle(R.string.send)
.setPositiveButton(R.string.send, null)
} else {
builder.setTitle(R.string.sign_transaction)
.setPositiveButton(R.string.sign, null)
}
builder.setView(R.layout.send)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, null)
.setNeutralButton(R.string.qr_code, null)
}

override fun onShowDialog() {
override fun onFirstShowDialog() {
if (arguments != null) {
val address = arguments!!.getString("address")
if (address != null) {
etAddress.setText(address)
etAmount.requestFocus()
}
arguments = null
}
}

override fun onShowDialog() {
setPaymentRequest(model.paymentRequest)

etAmount.addTextChangedListener(object : TextWatcher {
Expand Down Expand Up @@ -286,34 +297,30 @@ class SendPasswordDialog : PasswordDialog<Unit>() {
override fun onPassword(password: String) {
val wallet = daemonModel.wallet!!
wallet.callAttr("sign_transaction", model.tx, password)
if (! daemonModel.isConnected()) {
throw ToastException(R.string.not_connected)
}
val pr = sendDialog.model.paymentRequest
val result = if (pr != null) {
checkExpired(pr)
val refundAddr = wallet.callAttr("get_receiving_addresses").asList().get(0)
pr.callAttr("send_payment", model.tx.toString(), refundAddr)
} else {
daemonModel.network.callAttr("broadcast_transaction", model.tx)
}.asList()

val success = result.get(0).toBoolean()
if (success) {
setDescription(model.tx.callAttr("txid").toString(), model.description)
} else {
var message = result.get(1).toString()
val reError = Regex("^error: (.*)")
if (message.contains(reError)) {
message = message.replace(reError, "$1")
if (!sendDialog.unbroadcasted) {
if (!daemonModel.isConnected()) {
throw ToastException(R.string.not_connected)
}
val pr = sendDialog.model.paymentRequest
val result = if (pr != null) {
checkExpired(pr)
val refundAddr = wallet.callAttr("get_receiving_addresses").asList().get(0)
pr.callAttr("send_payment", model.tx.toString(), refundAddr)
} else {
daemonModel.network.callAttr("broadcast_transaction", model.tx)
}
throw ToastException(message)
checkBroadcastResult(result)
setDescription(model.tx.callAttr("txid").toString(), model.description)
}
}

override fun onPostExecute(result: Unit) {
sendDialog.dismiss()
toast(R.string.payment_sent, Toast.LENGTH_SHORT)
if (!sendDialog.unbroadcasted) {
toast(R.string.payment_sent, Toast.LENGTH_SHORT)
} else {
copyToClipboard(model.tx.toString(), R.string.signed_transaction)
}
}
}

Expand All @@ -323,3 +330,13 @@ private fun checkExpired(pr: PyObject) {
throw ToastException(R.string.payment_request_has)
}
}


fun checkBroadcastResult(result: PyObject) {
val success = result.asList().get(0).toBoolean()
if (!success) {
var message = result.asList().get(1).toString()
message = message.replace(Regex("^error: (.*)"), "$1")
throw ToastException(message)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def _(s):
_("ID")
_("Import addresses or private keys")
_("Invalid address")
_("Load transaction")
_("Made with <a href='https://chaquo.com/chaquopy'>Chaquopy</a>, the Python SDK for Android.")
_("New password")
_("New wallet")
Expand All @@ -44,8 +45,12 @@ def _(s):
_("Request")
_("Restore from seed")
_("Show seed")
_("Sign transaction")
_("Size")
_("Signed transaction")
_("The string you entered has been broadcast. Please check your transactions for confirmation.")
_("Transaction not found")
_("Type, paste, or scan a valid signed transaction in hex format below:")
_("Use a master key")
_("Wallet name is too long")
_("Wallet names cannot contain the '/' character. Please enter a different wallet name to proceed.")
Expand Down
9 changes: 9 additions & 0 deletions android/app/src/main/res/drawable/ic_paste_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M19,2h-4.18C14.4,0.84 13.3,0 12,0c-1.3,0 -2.4,0.84 -2.82,2L5,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,4c0,-1.1 -0.9,-2 -2,-2zM12,2c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM19,20L5,20L5,4h2v3h10L17,4h2v16z"/>
</vector>
57 changes: 57 additions & 0 deletions android/app/src/main/res/layout/load.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:id="@+id/textView4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:text="@string/type_paste"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

<EditText
android:id="@+id/etTransaction"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:inputType="textNoSuggestions"
android:minLines="3"
app:layout_constraintEnd_toStartOf="@+id/btnPaste"
app:layout_constraintStart_toStartOf="@+id/textView4"
app:layout_constraintTop_toBottomOf="@+id/textView4"
tools:text="@string/test_address">
<requestFocus/>
</EditText>

<!-- Bottom constraint keeps button aligned with text box before API 21 (see dimens.xml). -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/btnPaste"
style="@style/FAB.Dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/etTransaction"
app:layout_constraintEnd_toEndOf="@+id/textView4"
app:layout_constraintTop_toTopOf="@+id/etTransaction"
app:srcCompat="@drawable/ic_paste_24dp"/>



</androidx.constraintlayout.widget.ConstraintLayout>

</ScrollView>
11 changes: 11 additions & 0 deletions android/app/src/main/res/menu/wallet.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@
android:icon="@drawable/ic_key_24dp"
android:title="@string/show_seed"/>

<item
android:id="@+id/menuExportSigned"
android:icon="@drawable/ic_key_24dp"
android:title="@string/sign_transaction"/>

<item
android:id="@+id/menuLoadSigned"
android:icon="@drawable/ic_key_24dp"
android:title="@string/load_transaction"/>


<item
android:id="@+id/menuRename"
android:icon="@drawable/ic_edit_24dp"
Expand Down

0 comments on commit 2774b92

Please sign in to comment.