LBRY-Vault/gui/android.py
Neil Booth ae4cfc9f0b Unregister network callbacks from QT gui
Rework the callback system in QT to make this easy, and avoid
leaking window references that prevent the window from being
GC-ed on close
2015-11-13 23:36:29 +09:00

1017 lines
31 KiB
Python

#!/usr/bin/env python
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2011 thomasv@gitorious
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import
import android
from electrum import SimpleConfig, Wallet, WalletStorage, format_satoshis
from electrum.bitcoin import is_address, COIN
from electrum import util
from decimal import Decimal
import datetime, re
def modal_dialog(title, msg = None):
droid.dialogCreateAlert(title,msg)
droid.dialogSetPositiveButtonText('OK')
droid.dialogShow()
droid.dialogGetResponse()
droid.dialogDismiss()
def modal_input(title, msg, value = None, etype=None):
droid.dialogCreateInput(title, msg, value, etype)
droid.dialogSetPositiveButtonText('OK')
droid.dialogSetNegativeButtonText('Cancel')
droid.dialogShow()
response = droid.dialogGetResponse()
result = response.result
droid.dialogDismiss()
if result is None:
print "modal input: result is none"
return modal_input(title, msg, value, etype)
if result.get('which') == 'positive':
return result.get('value')
def modal_question(q, msg, pos_text = 'OK', neg_text = 'Cancel'):
droid.dialogCreateAlert(q, msg)
droid.dialogSetPositiveButtonText(pos_text)
droid.dialogSetNegativeButtonText(neg_text)
droid.dialogShow()
response = droid.dialogGetResponse()
result = response.result
droid.dialogDismiss()
if result is None:
print "modal question: result is none"
return modal_question(q,msg, pos_text, neg_text)
return result.get('which') == 'positive'
def edit_label(addr):
v = modal_input('Edit label', None, wallet.labels.get(addr))
if v is not None:
wallet.set_label(addr, v)
droid.fullSetProperty("labelTextView", "text", v)
def select_from_contacts():
title = 'Contacts:'
droid.dialogCreateAlert(title)
l = contacts.keys()
droid.dialogSetItems(l)
droid.dialogSetPositiveButtonText('New contact')
droid.dialogShow()
response = droid.dialogGetResponse().result
droid.dialogDismiss()
if response.get('which') == 'positive':
return 'newcontact'
result = response.get('item')
if result is not None:
t, v = contacts.get(result)
return v
def protocol_name(p):
if p == 't': return 'TCP'
if p == 's': return 'SSL'
def protocol_dialog(host, protocol, z):
droid.dialogCreateAlert('Protocol', host)
protocols = filter(lambda x: x in "ts", z.keys())
l = []
current = protocols.index(protocol)
for p in protocols:
l.append(protocol_name(p))
droid.dialogSetSingleChoiceItems(l, current)
droid.dialogSetPositiveButtonText('OK')
droid.dialogSetNegativeButtonText('Cancel')
droid.dialogShow()
response = droid.dialogGetResponse().result
selected_item = droid.dialogGetSelectedItems().result
droid.dialogDismiss()
if not response:
return
if not selected_item:
return
if response.get('which') == 'positive':
return protocols[selected_item[0]]
def make_layout(s, scrollable = False):
content = """
<LinearLayout
android:id="@+id/zz"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ff222222">
<TextView
android:id="@+id/textElectrum"
android:text="Electrum"
android:textSize="7pt"
android:textColor="#ff4444ff"
android:gravity="left"
android:layout_height="wrap_content"
android:layout_width="match_parent"
/>
</LinearLayout>
%s """%s
if scrollable:
content = """
<ScrollView
android:id="@+id/scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
%s
</LinearLayout>
</ScrollView>
"""%content
return """<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/background"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff000022">
%s
</LinearLayout>"""%content
def main_layout():
h = get_history_layout(15)
l = make_layout("""
<TextView android:id="@+id/balanceTextView"
android:layout_width="match_parent"
android:text=""
android:textColor="#ffffffff"
android:textAppearance="?android:attr/textAppearanceLarge"
android:padding="7dip"
android:textSize="8pt"
android:gravity="center_vertical|center_horizontal|left">
</TextView>
<TextView android:id="@+id/historyTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Recent transactions"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical|center_horizontal|center">
</TextView>
%s """%h,True)
return l
def qr_layout(addr, amount, message):
addr_view= """
<TextView android:id="@+id/addrTextView"
android:layout_width="match_parent"
android:layout_height="50"
android:text="%s"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical|center_horizontal|center">
</TextView>"""%addr
if amount:
amount_view = """
<TextView android:id="@+id/amountTextView"
android:layout_width="match_parent"
android:layout_height="50"
android:text="Amount: %s"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical|center_horizontal|center">
</TextView>"""%format_satoshis(amount)
else:
amount_view = ""
if message:
message_view = """
<TextView android:id="@+id/messageTextView"
android:layout_width="match_parent"
android:layout_height="50"
android:text="Message: %s"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical|center_horizontal|center">
</TextView>"""%message
else:
message_view = ""
return make_layout("""
%s
%s
%s
<ImageView
android:id="@+id/qrView"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="350"
android:antialias="false"
android:src="file:///sdcard/sl4a/qrcode.bmp" />
"""%(addr_view, amount_view, message_view), True)
payto_layout = make_layout("""
<TextView android:id="@+id/recipientTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Pay to:"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="left">
</TextView>
<EditText android:id="@+id/recipient"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="Tag Me" android:inputType="text">
</EditText>
<LinearLayout android:id="@+id/linearLayout1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button android:id="@+id/buttonQR" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="From QR code"></Button>
<Button android:id="@+id/buttonContacts" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="From Contacts"></Button>
</LinearLayout>
<TextView android:id="@+id/labelTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Message:"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="left">
</TextView>
<EditText android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="Tag Me" android:inputType="text">
</EditText>
<TextView android:id="@+id/amountLabelTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Amount:"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="left">
</TextView>
<EditText android:id="@+id/amount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="Tag Me" android:inputType="numberDecimal">
</EditText>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/linearLayout1">
<Button android:id="@+id/buttonPay" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="Send"></Button>
</LinearLayout>""",False)
settings_layout = make_layout(""" <ListView
android:id="@+id/myListView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />""")
def get_history_values(n):
values = []
h = wallet.get_history()
length = min(n, len(h))
for i in range(length):
tx_hash, conf, value, timestamp, balance = h[-i-1]
try:
dt = datetime.datetime.fromtimestamp( timestamp )
if dt.date() == dt.today().date():
time_str = str( dt.time() )
else:
time_str = str( dt.date() )
except Exception:
time_str = 'pending'
conf_str = 'v' if conf else 'o'
label, is_default_label = wallet.get_label(tx_hash)
label = label.replace('<','').replace('>','')
values.append((conf_str, ' ' + time_str, ' ' + format_satoshis(value, True), ' ' + label))
return values
def get_history_layout(n):
rows = ""
i = 0
values = get_history_values(n)
for v in values:
a,b,c,d = v
color = "#ff00ff00" if a == 'v' else "#ffff0000"
rows += """
<TableRow>
<TextView
android:id="@+id/hl_%d_col1"
android:layout_column="0"
android:text="%s"
android:textColor="%s"
android:padding="3" />
<TextView
android:id="@+id/hl_%d_col2"
android:layout_column="1"
android:text="%s"
android:padding="3" />
<TextView
android:id="@+id/hl_%d_col3"
android:layout_column="2"
android:text="%s"
android:padding="3" />
<TextView
android:id="@+id/hl_%d_col4"
android:layout_column="3"
android:text="%s"
android:padding="4" />
</TableRow>"""%(i,a,color,i,b,i,c,i,d)
i += 1
output = """
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="0,1,2,3">
%s
</TableLayout>"""% rows
return output
def set_history_layout(n):
values = get_history_values(n)
i = 0
for v in values:
a,b,c,d = v
droid.fullSetProperty("hl_%d_col1"%i,"text", a)
if a == 'v':
droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ff00ff00")
else:
droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ffff0000")
droid.fullSetProperty("hl_%d_col2"%i,"text", b)
droid.fullSetProperty("hl_%d_col3"%i,"text", c)
droid.fullSetProperty("hl_%d_col4"%i,"text", d)
i += 1
status_text = ''
def update_layout():
global status_text
if not network.is_connected():
text = "Not connected..."
elif not wallet.up_to_date:
text = "Synchronizing..."
else:
c, u, x = wallet.get_balance()
text = "Balance:"+format_satoshis(c)
if u:
text += ' [' + format_satoshis(u,True).strip() + ']'
if x:
text += ' [' + format_satoshis(x,True).strip() + ']'
# vibrate if status changed
if text != status_text:
if status_text and network.is_connected() and wallet.up_to_date:
droid.vibrate()
status_text = text
droid.fullSetProperty("balanceTextView", "text", status_text)
if wallet.up_to_date:
set_history_layout(15)
def pay_to(recipient, amount, label):
if wallet.use_encryption:
password = droid.dialogGetPassword('Password').result
if not password: return
else:
password = None
droid.dialogCreateSpinnerProgress("Electrum", "signing transaction...")
droid.dialogShow()
try:
tx = wallet.mktx([('address', recipient, amount)], password, config)
except Exception as e:
modal_dialog('error', e.message)
droid.dialogDismiss()
return
if label:
wallet.set_label(tx.hash(), label)
droid.dialogDismiss()
r, h = wallet.sendtx( tx )
if r:
modal_dialog('Payment sent', h)
return True
else:
modal_dialog('Error', h)
def make_new_contact():
code = droid.scanBarcode()
r = code.result
if r:
data = str(r['extras']['SCAN_RESULT']).strip()
if data:
if re.match('^bitcoin:', data):
out = util.parse_URI(data)
address = out.get('address')
elif is_address(data):
address = data
else:
address = None
if address:
if modal_question('Add to contacts?', address):
# fixme: ask for key
contacts[address] = ('address', address)
else:
modal_dialog('Invalid address', data)
do_refresh = False
def update_callback(event):
global do_refresh
print "gui callback", network.is_connected()
do_refresh = True
droid.eventPost("refresh",'z')
def main_loop():
global do_refresh
update_layout()
out = None
quitting = False
while out is None:
event = droid.eventWait(1000).result
if event is None:
if do_refresh:
update_layout()
do_refresh = False
continue
print "got event in main loop", repr(event)
if event == 'OK': continue
if event is None: continue
if not event.get("name"): continue
# request 2 taps before we exit
if event["name"]=="key":
if event["data"]["key"] == '4':
if quitting:
out = 'quit'
else:
quitting = True
else: quitting = False
if event["name"]=="click":
id=event["data"]["id"]
elif event["name"]=="settings":
out = 'settings'
elif event["name"] in menu_commands:
out = event["name"]
if out == 'contacts':
global contact_addr
contact_addr = select_from_contacts()
if contact_addr == 'newcontact':
make_new_contact()
contact_addr = None
if not contact_addr:
out = None
elif out == "receive":
global receive_addr
domain = wallet.addresses(include_change = False)
for addr in domain:
if not wallet.history.get(addr):
receive_addr = addr
break
else:
out = None
return out
def payto_loop():
global recipient
if recipient:
droid.fullSetProperty("recipient","text",recipient)
recipient = None
out = None
while out is None:
event = droid.eventWait().result
if not event: continue
print "got event in payto loop", event
if event == 'OK': continue
if not event.get("name"): continue
if event["name"] == "click":
id = event["data"]["id"]
if id=="buttonPay":
droid.fullQuery()
recipient = droid.fullQueryDetail("recipient").result.get('text')
message = droid.fullQueryDetail("message").result.get('text')
amount = droid.fullQueryDetail('amount').result.get('text')
if not is_address(recipient):
modal_dialog('Error','Invalid Bitcoin address')
continue
try:
amount = int(COIN * Decimal(amount))
except Exception:
modal_dialog('Error','Invalid amount')
continue
result = pay_to(recipient, amount, message)
if result:
out = 'main'
elif id=="buttonContacts":
addr = select_from_contacts()
droid.fullSetProperty("recipient", "text", addr)
elif id=="buttonQR":
code = droid.scanBarcode()
r = code.result
if r:
data = str(r['extras']['SCAN_RESULT']).strip()
if data:
print "data", data
if re.match('^bitcoin:', data):
rr = util.parse_URI(data)
amount = rr.get('amount')
address = rr.get('address')
message = rr.get('message', '')
if amount:
amount = str(Decimal(amount)/COIN)
droid.fullSetProperty("recipient", "text", address)
droid.fullSetProperty("amount", "text", amount)
droid.fullSetProperty("message", "text", message)
elif is_address(data):
droid.fullSetProperty("recipient", "text", data)
else:
modal_dialog('Error','cannot parse QR code\n'+data)
elif event["name"] in menu_commands:
out = event["name"]
elif event["name"]=="key":
if event["data"]["key"] == '4':
out = 'main'
#elif event["name"]=="screen":
# if event["data"]=="destroy":
# out = 'main'
return out
receive_addr = ''
receive_amount = None
receive_message = None
contact_addr = ''
recipient = ''
def receive_loop():
global receive_addr, receive_amount, receive_message
print "receive loop"
receive_URI = util.create_URI(receive_addr, receive_amount, receive_message)
make_bitmap(receive_URI)
droid.fullShow(qr_layout(receive_addr, receive_amount, receive_message))
out = None
while out is None:
event = droid.eventWait().result
if not event:
continue
elif event["name"]=="key":
if event["data"]["key"] == '4':
out = 'main'
elif event["name"]=="clipboard":
droid.setClipboard(receive_URI)
modal_dialog('URI copied to clipboard', receive_URI)
elif event["name"]=="amount":
amount = modal_input('Amount', 'Amount you want to receive (in BTC). ', format_satoshis(receive_amount) if receive_amount else None, "numberDecimal")
if amount is not None:
receive_amount = int(COIN * Decimal(amount)) if amount else None
out = 'receive'
elif event["name"]=="message":
message = modal_input('Message', 'Message in your request', receive_message)
if message is not None:
receive_message = unicode(message)
out = 'receive'
return out
def contacts_loop():
global recipient
out = None
while out is None:
event = droid.eventWait().result
print "got event", event
if event["name"]=="key":
if event["data"]["key"] == '4':
out = 'main'
elif event["name"]=="clipboard":
droid.setClipboard(contact_addr)
modal_dialog('Address copied to clipboard',contact_addr)
elif event["name"]=="edit":
edit_label(contact_addr)
elif event["name"]=="paytocontact":
recipient = contact_addr
out = 'send'
elif event["name"]=="deletecontact":
if modal_question('delete contact', contact_addr):
out = 'main'
return out
def server_dialog(servers):
droid.dialogCreateAlert("Public servers")
droid.dialogSetItems( servers.keys() )
droid.dialogSetPositiveButtonText('Private server')
droid.dialogShow()
response = droid.dialogGetResponse().result
droid.dialogDismiss()
if not response: return
if response.get('which') == 'positive':
return modal_input('Private server', None)
i = response.get('item')
if i is not None:
response = servers.keys()[i]
return response
def show_seed():
if wallet.use_encryption:
password = droid.dialogGetPassword('Seed').result
if not password: return
else:
password = None
try:
seed = wallet.get_mnemonic(password)
except Exception:
modal_dialog('error','incorrect password')
return
modal_dialog('Your seed is', seed)
def change_password_dialog():
if wallet.use_encryption:
password = droid.dialogGetPassword('Your wallet is encrypted').result
if password is None: return
else:
password = None
try:
wallet.check_password(password)
except Exception:
modal_dialog('error','incorrect password')
return
new_password = droid.dialogGetPassword('Choose a password').result
if new_password == None:
return
if new_password != '':
password2 = droid.dialogGetPassword('Confirm new password').result
if new_password != password2:
modal_dialog('error','passwords do not match')
return
wallet.update_password(password, new_password)
if new_password:
modal_dialog('Password updated','your wallet is encrypted')
else:
modal_dialog('No password','your wallet is not encrypted')
return True
def settings_loop():
def set_listview():
host, port, p, proxy_config, auto_connect = network.get_parameters()
fee = str(Decimal(wallet.fee_per_kb(config)) / COIN)
is_encrypted = 'yes' if wallet.use_encryption else 'no'
protocol = protocol_name(p)
droid.fullShow(settings_layout)
droid.fullSetList("myListView",['Server: ' + host, 'Protocol: '+ protocol, 'Port: '+port, 'Transaction fee/kb: '+fee, 'Password: '+is_encrypted, 'Seed'])
set_listview()
out = None
while out is None:
event = droid.eventWait()
event = event.result
print "got event", event
if event == 'OK': continue
if not event: continue
servers = network.get_servers()
name = event.get("name")
if not name: continue
if name == "itemclick":
pos = event["data"]["position"]
host, port, protocol, proxy_config, auto_connect = network.get_parameters()
network_changed = False
if pos == "0": #server
host = server_dialog(servers)
if host:
p = servers[host]
port = p[protocol]
network_changed = True
elif pos == "1": #protocol
if host in servers:
protocol = protocol_dialog(host, protocol, servers[host])
if protocol:
z = servers[host]
port = z[protocol]
network_changed = True
elif pos == "2": #port
a_port = modal_input('Port number', 'If you use a public server, this field is set automatically when you set the protocol', port, "number")
if a_port != port:
port = a_port
network_changed = True
elif pos == "3": #fee
fee = modal_input(
'Transaction fee',
'The fee will be this amount multiplied by the number of inputs in your transaction. ',
str(Decimal(wallet.fee_per_kb(config)) / COIN), "numberDecimal")
if fee:
try:
fee = int(COIN * Decimal(fee))
except Exception:
modal_dialog('error','invalid fee value')
config.set_key('fee_per_kb', fee)
set_listview()
elif pos == "4":
if change_password_dialog():
set_listview()
elif pos == "5":
show_seed()
if network_changed:
proxy = None
auto_connect = False
try:
network.set_parameters(host, port, protocol, proxy, auto_connect)
except Exception:
modal_dialog('error','invalid server')
set_listview()
elif name in menu_commands:
out = event["name"]
elif name == 'cancel':
out = 'main'
elif name == "key":
if event["data"]["key"] == '4':
out = 'main'
return out
def add_menu(s):
droid.clearOptionsMenu()
if s == 'main':
droid.addOptionsMenuItem("Send","send",None,"")
droid.addOptionsMenuItem("Receive","receive",None,"")
droid.addOptionsMenuItem("Contacts","contacts",None,"")
droid.addOptionsMenuItem("Settings","settings",None,"")
elif s == 'receive':
droid.addOptionsMenuItem("Copy","clipboard",None,"")
droid.addOptionsMenuItem("Amount","amount",None,"")
droid.addOptionsMenuItem("Message","message",None,"")
elif s == 'contacts':
droid.addOptionsMenuItem("Copy","clipboard",None,"")
droid.addOptionsMenuItem("Label","edit",None,"")
droid.addOptionsMenuItem("Pay to","paytocontact",None,"")
#droid.addOptionsMenuItem("Delete","deletecontact",None,"")
def make_bitmap(data):
# fixme: this is highly inefficient
droid.dialogCreateSpinnerProgress("please wait")
droid.dialogShow()
try:
import qrcode
from electrum import bmp
qr = qrcode.QRCode()
qr.add_data(data)
bmp.save_qrcode(qr,"/sdcard/sl4a/qrcode.bmp")
finally:
droid.dialogDismiss()
droid = android.Android()
menu_commands = ["send", "receive", "settings", "contacts", "main"]
wallet = None
network = None
contacts = None
config = None
class ElectrumGui:
def __init__(self, _config, _network, plugins):
global wallet, network, contacts, config
network = _network
config = _config
network.register_callback(update_callback, ['updated'])
contacts = util.StoreDict(config, 'contacts')
storage = WalletStorage(config.get_wallet_path())
if not storage.file_exists:
action = self.restore_or_create()
if not action:
exit()
password = droid.dialogGetPassword('Choose a password').result
if password:
password2 = droid.dialogGetPassword('Confirm password').result
if password != password2:
modal_dialog('Error','passwords do not match')
exit()
else:
# set to None if it's an empty string
password = None
if action == 'create':
wallet = Wallet(storage)
seed = wallet.make_seed()
modal_dialog('Your seed is:', seed)
wallet.add_seed(seed, password)
wallet.create_master_keys(password)
wallet.create_main_account(password)
elif action == 'restore':
seed = self.seed_dialog()
if not seed:
exit()
if not Wallet.is_seed(seed):
exit()
wallet = Wallet.from_seed(seed, password, storage)
else:
exit()
msg = "Creating wallet" if action == 'create' else "Restoring wallet"
droid.dialogCreateSpinnerProgress("Electrum", msg)
droid.dialogShow()
wallet.start_threads(network)
if action == 'restore':
wallet.wait_until_synchronized()
else:
wallet.synchronize()
droid.dialogDismiss()
droid.vibrate()
else:
wallet = Wallet(storage)
wallet.start_threads(network)
def main(self):
s = 'main'
while True:
add_menu(s)
if s == 'main':
droid.fullShow(main_layout())
s = main_loop()
elif s == 'send':
droid.fullShow(payto_layout)
s = payto_loop()
elif s == 'receive':
s = receive_loop()
elif s == 'contacts':
make_bitmap(contact_addr)
droid.fullShow(qr_layout(contact_addr, None, None))
s = contacts_loop()
elif s == 'settings':
s = settings_loop()
else:
break
droid.makeToast("Bye!")
def restore_or_create(self):
droid.dialogCreateAlert("Wallet not found","Do you want to create a new wallet, or restore an existing one?")
droid.dialogSetPositiveButtonText('Create')
droid.dialogSetNeutralButtonText('Restore')
droid.dialogSetNegativeButtonText('Cancel')
droid.dialogShow()
response = droid.dialogGetResponse().result
droid.dialogDismiss()
if not response: return
if response.get('which') == 'negative':
return
return 'restore' if response.get('which') == 'neutral' else 'create'
def seed_dialog(self):
if modal_question("Enter your seed", "Input method", 'QR Code', 'mnemonic'):
code = droid.scanBarcode()
r = code.result
if r:
seed = r['extras']['SCAN_RESULT']
else:
return
else:
seed = modal_input('Mnemonic', 'please enter your code')
return str(seed)