3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 from interface import WalletSynchronizer
23 from wallet import Wallet
24 from wallet import format_satoshis
25 from decimal import Decimal
31 droid = android.Android()
33 wallet.set_path("/sdcard/electrum.dat")
39 def select_from_contacts():
41 droid.dialogCreateAlert(title)
42 droid.dialogSetItems(wallet.addressbook)
44 response = droid.dialogGetResponse()
45 result = response.result.get('item')
47 if result is not None:
48 addr = wallet.addressbook[result]
52 def select_from_addresses():
53 droid.dialogCreateAlert("Addresses:")
55 for i in range(len(wallet.addresses)):
56 addr = wallet.addresses[i]
57 l.append( wallet.labels.get(addr,'') + ' ' + addr)
59 droid.dialogSetItems(l)
61 response = droid.dialogGetResponse()
62 result = response.result.get('item')
64 if result is not None:
65 addr = wallet.addresses[result]
71 <TextView android:id="@+id/titleTextView"
72 android:layout_width="match_parent"
73 android:layout_height="100"
74 android:text="Electrum"
75 android:textAppearance="?android:attr/textAppearanceLarge"
76 android:gravity="center"
77 android:textColor="0xff0055ff"
78 android:textSize="30" >
83 return """<?xml version="1.0" encoding="utf-8"?>
84 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
85 android:layout_width="match_parent"
86 android:layout_height="match_parent">
89 android:id="@+id/background"
90 android:orientation="vertical"
91 android:layout_width="match_parent"
92 android:layout_height="match_parent"
93 android:background="#ff000022">
97 <TextView android:id="@+id/balanceTextView"
98 android:layout_width="match_parent"
99 android:layout_height="wrap_content"
101 android:textAppearance="?android:attr/textAppearanceLarge"
102 android:gravity="left"
103 android:textColor="0xffffffff"
105 android:textSize="18" >
109 <TextView android:id="@+id/historyTextView"
110 android:layout_width="match_parent"
111 android:layout_height="70"
112 android:text="Recent transactions"
113 android:textAppearance="?android:attr/textAppearanceLarge"
114 android:gravity="center_vertical|center_horizontal|center">
120 android:layout_width="match_parent"
121 android:layout_height="wrap_content"
122 android:id="@+id/linearLayout1">
124 <Button android:id="@+id/buttonSend"
125 android:layout_width="wrap_content"
126 android:layout_height="wrap_content"
127 android:text=" Send ">
129 <Button android:id="@+id/buttonReceive"
130 android:layout_width="wrap_content"
131 android:layout_height="wrap_content"
132 android:text="Receive">
134 <Button android:id="@+id/buttonContacts"
135 android:layout_width="wrap_content"
136 android:layout_height="wrap_content"
137 android:text="Contacts">
144 """%(title, get_history_layout(15))
147 payto_layout="""<?xml version="1.0" encoding="utf-8"?>
148 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
149 android:id="@+id/background"
150 android:orientation="vertical"
151 android:layout_width="match_parent"
152 android:layout_height="match_parent"
153 android:background="#ff000022">
157 <TextView android:id="@+id/recipientTextView"
158 android:layout_width="match_parent"
159 android:layout_height="wrap_content"
160 android:text="Pay to:"
161 android:textAppearance="?android:attr/textAppearanceLarge"
162 android:gravity="left">
166 <EditText android:id="@+id/recipient"
167 android:layout_width="match_parent"
168 android:layout_height="wrap_content"
169 android:tag="Tag Me" android:inputType="textCapWords|textPhonetic|number">
172 <LinearLayout android:id="@+id/linearLayout1"
173 android:layout_width="match_parent"
174 android:layout_height="wrap_content">
175 <Button android:id="@+id/buttonQR" android:layout_width="wrap_content"
176 android:layout_height="wrap_content" android:text="Scan QR"></Button>
177 <Button android:id="@+id/buttonContacts" android:layout_width="wrap_content"
178 android:layout_height="wrap_content" android:text="Contacts"></Button>
182 <TextView android:id="@+id/labelTextView"
183 android:layout_width="match_parent"
184 android:layout_height="wrap_content"
185 android:text="Description:"
186 android:textAppearance="?android:attr/textAppearanceLarge"
187 android:gravity="left">
190 <EditText android:id="@+id/label"
191 android:layout_width="match_parent"
192 android:layout_height="wrap_content"
193 android:tag="Tag Me" android:inputType="textCapWords|textPhonetic|number">
196 <TextView android:id="@+id/amountLabelTextView"
197 android:layout_width="match_parent"
198 android:layout_height="wrap_content"
199 android:text="Amount:"
200 android:textAppearance="?android:attr/textAppearanceLarge"
201 android:gravity="left">
204 <EditText android:id="@+id/amount"
205 android:layout_width="match_parent"
206 android:layout_height="wrap_content"
207 android:tag="Tag Me" android:inputType="numberDecimal">
210 <LinearLayout android:layout_width="match_parent"
211 android:layout_height="wrap_content" android:id="@+id/linearLayout1">
212 <Button android:id="@+id/buttonPay" android:layout_width="wrap_content"
213 android:layout_height="wrap_content" android:text="Send"></Button>
214 <Button android:id="@+id/buttonCancelSend" android:layout_width="wrap_content"
215 android:layout_height="wrap_content" android:text="Cancel"></Button>
223 return """<?xml version="1.0" encoding="utf-8"?>
224 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
225 android:id="@+id/background"
226 android:orientation="vertical"
227 android:layout_width="match_parent"
228 android:layout_height="match_parent"
229 android:background="#ff000000">
232 </LinearLayout>"""%(title,s)
234 receive_layout = make_layout("""
235 <TextView android:id="@+id/receiveTextView"
236 android:layout_width="match_parent"
237 android:layout_height="wrap_content"
239 android:textAppearance="?android:attr/textAppearanceLarge"
240 android:gravity="left">
243 contacts_layout = make_layout("""
244 <TextView android:id="@+id/contactTextView"
245 android:layout_width="match_parent"
246 android:layout_height="wrap_content"
247 android:text="Contacts"
248 android:textAppearance="?android:attr/textAppearanceLarge"
249 android:gravity="left">
252 settings_layout = make_layout("""
253 <TextView android:id="@+id/serverTextView"
254 android:layout_width="match_parent"
255 android:layout_height="wrap_content"
256 android:text="Server:"
257 android:textAppearance="?android:attr/textAppearanceLarge"
258 android:gravity="left">
261 <EditText android:id="@+id/server"
262 android:layout_width="match_parent"
263 android:layout_height="wrap_content"
264 android:tag="Tag Me" android:inputType="*">
267 <LinearLayout android:layout_width="match_parent"
268 android:layout_height="wrap_content" android:id="@+id/linearLayout1">
269 <Button android:id="@+id/buttonServer" android:layout_width="wrap_content"
270 android:layout_height="wrap_content" android:text="Server List"></Button>
271 <Button android:id="@+id/buttonSave" android:layout_width="wrap_content"
272 android:layout_height="wrap_content" android:text="Save"></Button>
273 <Button android:id="@+id/buttonCancel" android:layout_width="wrap_content"
274 android:layout_height="wrap_content" android:text="Cancel"></Button>
279 def get_history_values(n):
281 h = wallet.get_tx_history()
286 dt = datetime.datetime.fromtimestamp( line['timestamp'] )
287 if dt.date() == dt.today().date():
288 time_str = str( dt.time() )
290 time_str = str( dt.date() )
294 print line['timestamp']
298 label = line.get('label')
299 #if not label: label = line['tx_hash']
300 is_default_label = (label == '') or (label is None)
301 if is_default_label: label = line['default_label']
302 values.append((conf, ' ' + time_str, ' ' + format_satoshis(v,True), ' ' + label ))
307 def get_history_layout(n):
310 values = get_history_values(n)
313 color = "0xff00ff00" if a == 'v' else "0xffff0000"
317 android:id="@+id/hl_%d_col1"
318 android:layout_column="0"
320 android:textColor="%s"
321 android:padding="3" />
323 android:id="@+id/hl_%d_col2"
324 android:layout_column="1"
326 android:padding="3" />
328 android:id="@+id/hl_%d_col3"
329 android:layout_column="2"
331 android:padding="3" />
333 android:id="@+id/hl_%d_col4"
334 android:layout_column="3"
336 android:padding="4" />
337 </TableRow>"""%(i,a,color,i,b,i,c,i,d)
341 <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
342 android:layout_width="fill_parent"
343 android:layout_height="wrap_content"
344 android:stretchColumns="0,1,2,3">
346 </TableLayout>"""% rows
349 def set_history_layout(n):
350 values = get_history_values(n)
354 droid.fullSetProperty("hl_%d_col1"%i,"text", a)
357 droid.fullSetProperty("hl_%d_col1"%i, "textColor","0xff00ff00")
359 droid.fullSetProperty("hl_%d_col1"%i, "textColor","0xffff0000")
361 droid.fullSetProperty("hl_%d_col2"%i,"text", b)
362 droid.fullSetProperty("hl_%d_col3"%i,"text", c)
363 droid.fullSetProperty("hl_%d_col4"%i,"text", d)
370 if not wallet.interface.is_connected:
371 text = "Not connected..."
372 elif wallet.blocks == 0:
373 text = "Server not ready"
374 elif not wallet.up_to_date:
375 text = "Synchronizing..."
377 c, u = wallet.get_balance()
378 text = "Balance:"+format_satoshis(c)
379 if u : text += '['+ format_satoshis(u,True)+']'
381 droid.fullSetProperty("balanceTextView", "text", text)
383 if wallet.was_updated and wallet.up_to_date:
384 wallet.was_updated = False
385 set_history_layout(15)
391 def pay_to(recipient, amount, fee, label):
393 if wallet.use_encryption:
394 password = droid.dialogGetPassword('Password').result
395 print "password", password
399 droid.dialogCreateSpinnerProgress("Electrum", "signing transaction...")
401 tx = wallet.mktx( recipient, amount, label, password, fee)
403 droid.dialogDismiss()
406 r, h = wallet.sendtx( tx )
407 droid.dialogCreateAlert('tx sent', h)
408 droid.dialogSetPositiveButtonText('OK')
410 response = droid.dialogGetResponse().result
411 droid.dialogDismiss()
422 if not wallet.file_exists:
423 droid.dialogCreateAlert("wallet file not found")
424 droid.dialogSetPositiveButtonText('OK')
426 resp = droid.dialogGetResponse().result
429 code = droid.scanBarcode()
432 seed = r['extras']['SCAN_RESULT']
436 droid.dialogCreateAlert('seed', seed)
437 droid.dialogSetPositiveButtonText('OK')
438 droid.dialogSetNegativeButtonText('Cancel')
440 response = droid.dialogGetResponse().result
441 droid.dialogDismiss()
444 wallet.seed = str(seed)
445 wallet.init_mpk( wallet.seed )
446 droid.dialogCreateSpinnerProgress("Electrum", "recovering wallet...")
448 WalletSynchronizer(wallet,True).start()
451 droid.dialogDismiss()
454 if wallet.is_found():
455 # history and addressbook
456 wallet.update_tx_history()
457 wallet.fill_addressbook()
458 droid.dialogCreateAlert("recovery successful")
462 droid.dialogCreateSpinnerProgress("wallet not found")
467 WalletSynchronizer(wallet,True).start()
471 droid.addOptionsMenuItem("Network","settings",None,"")
472 droid.addOptionsMenuItem("New contact","newcontact",None,"")
473 droid.addOptionsMenuItem("Quit","quit",None,"")
479 droid.fullShow(main_layout())
484 event = droid.eventWait(1000).result # wait for 1 second
489 print "got event in main loop", event
491 if event["name"]=="click":
492 id=event["data"]["id"]
497 if id=="buttonContacts":
499 contact_addr = select_from_contacts()
503 elif id=="buttonReceive":
505 receive_addr = select_from_addresses()
509 elif event["name"]=="settings":
512 elif event["name"]=="newcontact":
513 code = droid.scanBarcode()
516 address = r['extras']['SCAN_RESULT']
518 if wallet.is_valid(address):
519 droid.dialogCreateAlert('Add to contacts?', address)
520 droid.dialogSetPositiveButtonText('OK')
521 droid.dialogSetNegativeButtonText('Cancel')
523 response = droid.dialogGetResponse().result
524 droid.dialogDismiss()
526 if response.get('which') == 'positive':
527 wallet.addressbook.append(address)
530 droid.dialogCreateAlert('Invalid address', address)
531 droid.dialogSetPositiveButtonText('OK')
533 response = droid.dialogGetResponse().result
534 droid.dialogDismiss()
536 elif event["name"]=="key":
537 if event["data"]["key"] == '4':
540 elif event["name"]=="quit":
546 droid.fullShow(payto_layout)
549 event = droid.eventWait().result
550 print "got event in payto loop", event
552 if event["name"] == "click":
553 id = event["data"]["id"]
558 recipient = droid.fullQueryDetail("recipient").result.get('text')
559 label = droid.fullQueryDetail("label").result.get('text')
560 amount = droid.fullQueryDetail('amount').result.get('text')
562 amount = int( 100000000 * Decimal(amount) )
563 fee = int( 100000000 * Decimal(fee) )
564 result = pay_to(recipient, amount, fee, label)
566 droid.dialogCreateAlert('result', result)
567 droid.dialogSetPositiveButtonText('OK')
569 droid.dialogGetResponse()
570 droid.dialogDismiss()
573 elif id=="buttonContacts":
574 addr = select_from_contacts()
575 droid.fullSetProperty("recipient","text",addr)
578 code = droid.scanBarcode()
581 addr = r['extras']['SCAN_RESULT']
583 droid.fullSetProperty("recipient","text",addr)
585 elif id=="buttonCancelSend":
588 elif event["name"]=="settings":
591 elif event["name"]=="quit":
594 elif event["name"]=="key":
595 if event["data"]["key"] == '4':
598 #elif event["name"]=="screen":
599 # if event["data"]=="destroy":
610 droid.fullShow(receive_layout)
611 droid.fullSetProperty("receiveTextView","text", receive_addr)
614 event = droid.eventWait().result
615 print "got event", event
616 if event["name"] == "click":
618 if event["data"]["text"] == "OK":
621 elif event["name"]=="key":
622 if event["data"]["key"] == '4':
628 droid.fullShow(contacts_layout)
629 droid.fullSetProperty("contactTextView","text", contact_addr)
632 event = droid.eventWait().result
633 print "got event", event
634 if event["name"] == "click":
636 if event["data"]["text"] == "OK":
639 elif event["name"]=="key":
640 if event["data"]["key"] == '4':
646 def server_dialog(plist):
647 droid.dialogCreateAlert("servers")
648 droid.dialogSetItems( plist.keys() )
650 i = droid.dialogGetResponse().result.get('item')
651 droid.dialogDismiss()
653 response = plist.keys()[i]
657 def protocol_dialog(plist):
658 options=["TCP","HTTP","native"]
659 droid.dialogCreateAlert("Protocol")
660 droid.dialogSetSingleChoiceItems(options)
665 droid.fullShow(settings_layout)
666 droid.fullSetProperty("server","text",wallet.server)
670 event = droid.eventWait().result
671 if event["name"] == "click":
673 id = event["data"]["id"]
675 if id=="buttonServer":
677 for item in wallet.interface.servers:
681 protocol, port = item2
685 host = server_dialog(plist)
688 srv = host + ':' + port + ':t'
689 droid.fullSetProperty("server","text",srv)
691 elif id=="buttonSave":
693 srv = droid.fullQueryDetail("server").result.get('text')
695 wallet.set_server(srv)
698 droid.dialogCreateAlert('error')
699 droid.dialogSetPositiveButtonText('OK')
701 droid.dialogGetResponse()
702 droid.dialogDismiss()
704 elif id=="buttonCancel":
707 elif event["name"] == "key":
708 if event["data"]["key"] == '4':
711 elif event["name"]=="quit":
727 elif s == 'contacts':
729 elif s == 'settings':
731 elif s == 'contacts':
737 droid.makeToast("Bye!")