You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1011 lines
31 KiB
1011 lines
31 KiB
#!/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 |
|
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 = wallet.get_balance() |
|
text = "Balance:"+format_satoshis(c) |
|
if u : text += ' [' + format_satoshis(u,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) |
|
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): |
|
address, _, _, _, _ = util.parse_URI(data) |
|
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(): |
|
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( 100000000 * 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): |
|
payto, amount, label, message, _ = util.parse_URI(data) |
|
if amount: |
|
amount = str(amount/100000000) |
|
droid.fullSetProperty("recipient", "text", payto) |
|
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(100000000 * 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)/100000000 ) |
|
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)/100000000 ), "numberDecimal") |
|
if fee: |
|
try: |
|
fee = int( 100000000 * Decimal(fee) ) |
|
except Exception: |
|
modal_dialog('error','invalid fee value') |
|
wallet.set_fee(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 |
|
|
|
class ElectrumGui: |
|
|
|
def __init__(self, config, _network): |
|
global wallet, network, contacts |
|
network = _network |
|
network.register_callback('updated', update_callback) |
|
network.register_callback('connected', update_callback) |
|
network.register_callback('disconnected', update_callback) |
|
network.register_callback('disconnecting', update_callback) |
|
|
|
contacts = util.StoreDict(config, 'contacts') |
|
|
|
storage = WalletStorage(config) |
|
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.restore(lambda x: None) |
|
else: |
|
wallet.synchronize() |
|
droid.dialogDismiss() |
|
droid.vibrate() |
|
|
|
else: |
|
wallet = Wallet(storage) |
|
wallet.start_threads(network) |
|
|
|
|
|
def main(self, url): |
|
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) |
|
|
|
|
|
|