23c368acef4477e3b9d9fe3ab3d8423da3494463
[electrum-nvc.git] / gui / gui_android.py
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
5 #
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.
10 #
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.
15 #
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/>.
18
19
20
21
22 import android
23
24 from electrum import SimpleConfig, Interface, WalletSynchronizer, Wallet, format_satoshis, mnemonic_encode, mnemonic_decode, is_valid
25 from electrum import util
26 from decimal import Decimal
27 import datetime, re
28
29
30
31 def modal_dialog(title, msg = None):
32     droid.dialogCreateAlert(title,msg)
33     droid.dialogSetPositiveButtonText('OK')
34     droid.dialogShow()
35     droid.dialogGetResponse()
36     droid.dialogDismiss()
37
38 def modal_input(title, msg, value = None, etype=None):
39     droid.dialogCreateInput(title, msg, value, etype)
40     droid.dialogSetPositiveButtonText('OK')
41     droid.dialogSetNegativeButtonText('Cancel')
42     droid.dialogShow()
43     response = droid.dialogGetResponse()
44     result = response.result
45     if result is None:
46         print "modal input: result is none"
47         return False
48     droid.dialogDismiss()
49     if result.get('which') == 'positive':
50         return result.get('value')
51
52 def modal_question(q, msg, pos_text = 'OK', neg_text = 'Cancel'):
53     droid.dialogCreateAlert(q, msg)
54     droid.dialogSetPositiveButtonText(pos_text)
55     droid.dialogSetNegativeButtonText(neg_text)
56     droid.dialogShow()
57     response = droid.dialogGetResponse()
58     result = response.result
59     if result is None:
60         print "modal question: result is none"
61         return False
62     droid.dialogDismiss()
63     return result.get('which') == 'positive'
64
65 def edit_label(addr):
66     v = modal_input('Edit label',None,wallet.labels.get(addr))
67     if v is not None:
68         if v:
69             wallet.labels[addr] = v
70         else:
71             if addr in wallet.labels.keys():
72                 wallet.labels.pop(addr)
73         wallet.update_tx_history()
74         wallet.save()
75         droid.fullSetProperty("labelTextView", "text", v)
76
77 def select_from_contacts():
78     title = 'Contacts:'
79     droid.dialogCreateAlert(title)
80     l = []
81     for i in range(len(wallet.addressbook)):
82         addr = wallet.addressbook[i]
83         label = wallet.labels.get(addr,addr)
84         l.append( label )
85     droid.dialogSetItems(l)
86     droid.dialogSetPositiveButtonText('New contact')
87     droid.dialogShow()
88     response = droid.dialogGetResponse().result
89     droid.dialogDismiss()
90
91     if response.get('which') == 'positive':
92         return 'newcontact'
93
94     result = response.get('item')
95     print result
96     if result is not None:
97         addr = wallet.addressbook[result]
98         return addr
99
100
101 def select_from_addresses():
102     droid.dialogCreateAlert("Addresses:")
103     l = []
104     addresses = wallet.addresses()
105     for i in range(len(addresses)):
106         addr = addresses[i]
107         label = wallet.labels.get(addr,addr)
108         l.append( label )
109     droid.dialogSetItems(l)
110     droid.dialogShow()
111     response = droid.dialogGetResponse()
112     result = response.result.get('item')
113     droid.dialogDismiss()
114     if result is not None:
115         addr = addresses[result]
116         return addr
117
118
119 def protocol_name(p):
120     if p == 't': return 'TCP'
121     if p == 'h': return 'HTTP'
122     if p == 's': return 'TCP/SSL'
123     if p == 'g': return 'HTTPS'
124
125
126 def protocol_dialog(host, protocol, z):
127     droid.dialogCreateAlert('Protocol',host)
128     if z:
129         protocols = z.keys()
130     else:
131         protocols = 'thsg'
132     l = []
133     current = protocols.index(protocol)
134     for p in protocols:
135         l.append(protocol_name(p))
136     droid.dialogSetSingleChoiceItems(l, current)
137     droid.dialogSetPositiveButtonText('OK')
138     droid.dialogSetNegativeButtonText('Cancel')
139     droid.dialogShow()
140     response = droid.dialogGetResponse().result
141     selected_item = droid.dialogGetSelectedItems().result
142     droid.dialogDismiss()
143
144     if not response: return
145     if not selected_item: return
146     if response.get('which') == 'positive':
147         p = protocols[selected_item[0]]
148         port = z[p]
149         return host + ':' + port + ':' + p
150
151
152
153
154 def make_layout(s, scrollable = False):
155     content = """
156
157       <LinearLayout 
158         android:id="@+id/zz"
159         android:layout_width="match_parent"
160         android:layout_height="wrap_content" 
161         android:background="#ff222222">
162
163         <TextView
164           android:id="@+id/textElectrum"
165           android:text="Electrum"
166           android:textSize="7pt"
167           android:textColor="#ff4444ff"
168           android:gravity="left"
169           android:layout_height="wrap_content"
170           android:layout_width="match_parent"
171         />
172       </LinearLayout>
173
174         %s   """%s
175
176     if scrollable:
177         content = """
178       <ScrollView 
179         android:id="@+id/scrollview"
180         android:layout_width="match_parent"
181         android:layout_height="match_parent" >
182
183       <LinearLayout
184         android:orientation="vertical" 
185         android:layout_width="match_parent"
186         android:layout_height="wrap_content" >
187
188       %s
189
190       </LinearLayout>
191       </ScrollView>
192       """%content
193
194
195     return """<?xml version="1.0" encoding="utf-8"?>
196       <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
197         android:id="@+id/background"
198         android:orientation="vertical" 
199         android:layout_width="match_parent"
200         android:layout_height="match_parent" 
201         android:background="#ff000022">
202
203       %s 
204       </LinearLayout>"""%content
205
206
207
208
209 def main_layout():
210     return make_layout("""
211         <TextView android:id="@+id/balanceTextView" 
212                 android:layout_width="match_parent"
213                 android:text=""
214                 android:textColor="#ffffffff"
215                 android:textAppearance="?android:attr/textAppearanceLarge" 
216                 android:padding="7dip"
217                 android:textSize="8pt"
218                 android:gravity="center_vertical|center_horizontal|left">
219         </TextView>
220
221         <TextView android:id="@+id/historyTextView" 
222                 android:layout_width="match_parent"
223                 android:layout_height="wrap_content" 
224                 android:text="Recent transactions"
225                 android:textAppearance="?android:attr/textAppearanceLarge" 
226                 android:gravity="center_vertical|center_horizontal|center">
227         </TextView>
228
229         %s """%get_history_layout(15),True)
230
231
232
233 def qr_layout(addr):
234     return make_layout("""
235
236      <TextView android:id="@+id/addrTextView" 
237                 android:layout_width="match_parent"
238                 android:layout_height="50" 
239                 android:text="%s"
240                 android:textAppearance="?android:attr/textAppearanceLarge" 
241                 android:gravity="center_vertical|center_horizontal|center">
242      </TextView>
243
244      <ImageView
245         android:id="@+id/qrView"
246         android:gravity="center"
247         android:layout_width="match_parent"
248         android:layout_height="350"
249         android:antialias="false"
250         android:src="file:///sdcard/sl4a/qrcode.bmp" /> 
251
252      <TextView android:id="@+id/labelTextView" 
253                 android:layout_width="match_parent"
254                 android:layout_height="50" 
255                 android:text="%s"
256                 android:textAppearance="?android:attr/textAppearanceLarge" 
257                 android:gravity="center_vertical|center_horizontal|center">
258      </TextView>
259
260      """%(addr,wallet.labels.get(addr,'')), True)
261
262 payto_layout = make_layout("""
263
264         <TextView android:id="@+id/recipientTextView" 
265                 android:layout_width="match_parent"
266                 android:layout_height="wrap_content" 
267                 android:text="Pay to:"
268                 android:textAppearance="?android:attr/textAppearanceLarge" 
269                 android:gravity="left">
270         </TextView>
271
272
273         <EditText android:id="@+id/recipient"
274                 android:layout_width="match_parent"
275                 android:layout_height="wrap_content" 
276                 android:tag="Tag Me" android:inputType="text">
277         </EditText>
278
279         <LinearLayout android:id="@+id/linearLayout1"
280                 android:layout_width="match_parent"
281                 android:layout_height="wrap_content">
282                 <Button android:id="@+id/buttonQR" android:layout_width="wrap_content"
283                         android:layout_height="wrap_content" android:text="From QR code"></Button>
284                 <Button android:id="@+id/buttonContacts" android:layout_width="wrap_content"
285                         android:layout_height="wrap_content" android:text="From Contacts"></Button>
286         </LinearLayout>
287
288
289         <TextView android:id="@+id/labelTextView" 
290                 android:layout_width="match_parent"
291                 android:layout_height="wrap_content" 
292                 android:text="Description:"
293                 android:textAppearance="?android:attr/textAppearanceLarge" 
294                 android:gravity="left">
295         </TextView>
296
297         <EditText android:id="@+id/label"
298                 android:layout_width="match_parent"
299                 android:layout_height="wrap_content" 
300                 android:tag="Tag Me" android:inputType="text">
301         </EditText>
302
303         <TextView android:id="@+id/amountLabelTextView" 
304                 android:layout_width="match_parent"
305                 android:layout_height="wrap_content" 
306                 android:text="Amount:"
307                 android:textAppearance="?android:attr/textAppearanceLarge" 
308                 android:gravity="left">
309         </TextView>
310
311         <EditText android:id="@+id/amount"
312                 android:layout_width="match_parent"
313                 android:layout_height="wrap_content" 
314                 android:tag="Tag Me" android:inputType="numberDecimal">
315         </EditText>
316
317         <LinearLayout android:layout_width="match_parent"
318                 android:layout_height="wrap_content" android:id="@+id/linearLayout1">
319                 <Button android:id="@+id/buttonPay" android:layout_width="wrap_content"
320                         android:layout_height="wrap_content" android:text="Send"></Button>
321         </LinearLayout>""",False)
322
323
324
325 settings_layout = make_layout(""" <ListView
326            android:id="@+id/myListView" 
327            android:layout_width="match_parent"
328            android:layout_height="wrap_content" />""")
329
330
331
332 def get_history_values(n):
333     values = []
334     h = wallet.get_tx_history()
335     length = min(n, len(h))
336     for i in range(length):
337         tx_hash, conf, is_mine, value, fee, balance, timestamp = h[-i-1]
338         try:
339             dt = datetime.datetime.fromtimestamp( timestamp )
340             if dt.date() == dt.today().date():
341                 time_str = str( dt.time() )
342             else:
343                 time_str = str( dt.date() )
344         except:
345             time_str = 'pending'
346
347         conf_str = 'v' if conf else 'o'
348         label, is_default_label = wallet.get_label(tx_hash)
349         values.append((conf_str, '  ' + time_str, '  ' + format_satoshis(value,True), '  ' + label ))
350
351     return values
352
353
354 def get_history_layout(n):
355     rows = ""
356     i = 0
357     values = get_history_values(n)
358     for v in values:
359         a,b,c,d = v
360         color = "#ff00ff00" if a == 'v' else "#ffff0000"
361         rows += """
362         <TableRow>
363           <TextView
364             android:id="@+id/hl_%d_col1" 
365             android:layout_column="0"
366             android:text="%s"
367             android:textColor="%s"
368             android:padding="3" />
369           <TextView
370             android:id="@+id/hl_%d_col2" 
371             android:layout_column="1"
372             android:text="%s"
373             android:padding="3" />
374           <TextView
375             android:id="@+id/hl_%d_col3" 
376             android:layout_column="2"
377             android:text="%s"
378             android:padding="3" />
379           <TextView
380             android:id="@+id/hl_%d_col4" 
381             android:layout_column="3"
382             android:text="%s"
383             android:padding="4" />
384         </TableRow>"""%(i,a,color,i,b,i,c,i,d)
385         i += 1
386
387     output = """
388 <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
389     android:layout_width="fill_parent"
390     android:layout_height="wrap_content"
391     android:stretchColumns="0,1,2,3">
392     %s
393 </TableLayout>"""% rows
394     return output
395
396
397 def set_history_layout(n):
398     values = get_history_values(n)
399     i = 0
400     for v in values:
401         a,b,c,d = v
402         droid.fullSetProperty("hl_%d_col1"%i,"text", a)
403
404         if a == 'v':
405             droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ff00ff00")
406         else:
407             droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ffff0000")
408
409         droid.fullSetProperty("hl_%d_col2"%i,"text", b)
410         droid.fullSetProperty("hl_%d_col3"%i,"text", c)
411         droid.fullSetProperty("hl_%d_col4"%i,"text", d)
412         i += 1
413
414
415
416
417 status_text = ''
418 def update_layout():
419     global status_text
420     if not wallet.interface.is_connected:
421         text = "Not connected..."
422     elif not wallet.up_to_date:
423         text = "Synchronizing..."
424     else:
425         c, u = wallet.get_balance()
426         text = "Balance:"+format_satoshis(c) 
427         if u : text += '   [' + format_satoshis(u,True).strip() + ']'
428
429
430     # vibrate if status changed
431     if text != status_text:
432         if status_text and wallet.interface.is_connected and wallet.up_to_date:
433             droid.vibrate()
434         status_text = text
435
436     droid.fullSetProperty("balanceTextView", "text", status_text)
437
438     if wallet.up_to_date:
439         set_history_layout(15)
440
441
442
443
444 def pay_to(recipient, amount, fee, label):
445
446     if wallet.use_encryption:
447         password  = droid.dialogGetPassword('Password').result
448         if not password: return
449     else:
450         password = None
451
452     droid.dialogCreateSpinnerProgress("Electrum", "signing transaction...")
453     droid.dialogShow()
454
455     try:
456         tx = wallet.mktx( [(recipient, amount)], password, fee)
457     except BaseException, e:
458         modal_dialog('error', e.message)
459         droid.dialogDismiss()
460         return
461
462     if label: 
463         self.wallet.labels[tx.hash()] = label
464
465     droid.dialogDismiss()
466
467     r, h = wallet.sendtx( tx )
468     if r:
469         modal_dialog('Payment sent', h)
470         return True
471     else:
472         modal_dialog('Error', h)
473
474
475
476
477
478
479
480 def make_new_contact():
481     code = droid.scanBarcode()
482     r = code.result
483     if r:
484         data = r['extras']['SCAN_RESULT']
485         if data:
486             if re.match('^bitcoin:', data):
487                 address, _, _, _, _, _, _ = util.parse_url(data)
488             elif is_valid(data):
489                 address = data
490             else:
491                 address = None
492             if address:
493                 if modal_question('Add to contacts?', address):
494                     wallet.addressbook.append(address)
495                     wallet.save()
496         else:
497             modal_dialog('Invalid address', data)
498
499
500 do_refresh = False
501
502 def update_callback():
503     global do_refresh
504     print "gui callback", wallet.interface.is_connected, wallet.up_to_date
505     do_refresh = True
506     droid.eventPost("refresh",'z')
507
508 def main_loop():
509     global do_refresh
510
511     update_layout()
512     out = None
513     quitting = False
514     while out is None:
515
516         event = droid.eventWait(1000).result
517         if event is None:
518             if do_refresh: 
519                 update_layout()
520                 do_refresh = False
521             continue
522
523         print "got event in main loop", repr(event)
524         if event == 'OK': continue
525         if event is None: continue
526         if not event.get("name"): continue
527
528         # request 2 taps before we exit
529         if event["name"]=="key":
530             if event["data"]["key"] == '4':
531                 if quitting:
532                     out = 'quit'
533                 else: 
534                     quitting = True
535         else: quitting = False
536
537         if event["name"]=="click":
538             id=event["data"]["id"]
539
540         elif event["name"]=="settings":
541             out = 'settings'
542
543         elif event["name"] in menu_commands:
544             out = event["name"]
545
546             if out == 'contacts':
547                 global contact_addr
548                 contact_addr = select_from_contacts()
549                 if contact_addr == 'newcontact':
550                     make_new_contact()
551                     contact_addr = None
552                 if not contact_addr:
553                     out = None
554
555             elif out == "receive":
556                 global receive_addr
557                 receive_addr = select_from_addresses()
558                 if receive_addr:
559                     amount = modal_input('Amount', 'Amount you want receive. ', '', "numberDecimal")
560                     if amount:
561                         receive_addr = 'bitcoin:%s?amount=%s'%(receive_addr, amount)
562
563                 if not receive_addr:
564                     out = None
565
566
567     return out
568                     
569
570 def payto_loop():
571     global recipient
572     if recipient:
573         droid.fullSetProperty("recipient","text",recipient)
574         recipient = None
575
576     out = None
577     while out is None:
578         event = droid.eventWait().result
579         if not event: continue
580         print "got event in payto loop", event
581         if event == 'OK': continue
582         if not event.get("name"): continue
583
584         if event["name"] == "click":
585             id = event["data"]["id"]
586
587             if id=="buttonPay":
588
589                 droid.fullQuery()
590                 recipient = droid.fullQueryDetail("recipient").result.get('text')
591                 label  = droid.fullQueryDetail("label").result.get('text')
592                 amount = droid.fullQueryDetail('amount').result.get('text')
593
594                 if not is_valid(recipient):
595                     modal_dialog('Error','Invalid Bitcoin address')
596                     continue
597
598                 try:
599                     amount = int( 100000000 * Decimal(amount) )
600                 except:
601                     modal_dialog('Error','Invalid amount')
602                     continue
603
604                 result = pay_to(recipient, amount, wallet.fee, label)
605                 if result:
606                     out = 'main'
607
608             elif id=="buttonContacts":
609                 addr = select_from_contacts()
610                 droid.fullSetProperty("recipient","text",addr)
611
612             elif id=="buttonQR":
613                 code = droid.scanBarcode()
614                 r = code.result
615                 if r:
616                     data = r['extras']['SCAN_RESULT']
617                     if data:
618                         if re.match('^bitcoin:', data):
619                             payto, amount, label, _, _, _, _ = util.parse_url(data)
620                             droid.fullSetProperty("recipient", "text",payto)
621                             droid.fullSetProperty("amount", "text", amount)
622                             droid.fullSetProperty("label", "text", label)
623                         else:
624                             droid.fullSetProperty("recipient", "text", data)
625
626                     
627         elif event["name"] in menu_commands:
628             out = event["name"]
629
630         elif event["name"]=="key":
631             if event["data"]["key"] == '4':
632                 out = 'main'
633
634         #elif event["name"]=="screen":
635         #    if event["data"]=="destroy":
636         #        out = 'main'
637
638     return out
639
640
641 receive_addr = ''
642 contact_addr = ''
643 recipient = ''
644
645 def receive_loop():
646     out = None
647     while out is None:
648         event = droid.eventWait().result
649         print "got event", event
650         if event["name"]=="key":
651             if event["data"]["key"] == '4':
652                 out = 'main'
653
654         elif event["name"]=="clipboard":
655             droid.setClipboard(receive_addr)
656             modal_dialog('Address copied to clipboard',receive_addr)
657
658         elif event["name"]=="edit":
659             edit_label(receive_addr)
660
661     return out
662
663 def contacts_loop():
664     global recipient
665     out = None
666     while out is None:
667         event = droid.eventWait().result
668         print "got event", event
669         if event["name"]=="key":
670             if event["data"]["key"] == '4':
671                 out = 'main'
672
673         elif event["name"]=="clipboard":
674             droid.setClipboard(contact_addr)
675             modal_dialog('Address copied to clipboard',contact_addr)
676
677         elif event["name"]=="edit":
678             edit_label(contact_addr)
679
680         elif event["name"]=="paytocontact":
681             recipient = contact_addr
682             out = 'send'
683
684         elif event["name"]=="deletecontact":
685             if modal_question('delete contact', contact_addr):
686                 out = 'main'
687
688     return out
689
690
691 def server_dialog(plist):
692     droid.dialogCreateAlert("Public servers")
693     droid.dialogSetItems( plist.keys() )
694     droid.dialogSetPositiveButtonText('Private server')
695     droid.dialogShow()
696     response = droid.dialogGetResponse().result
697     droid.dialogDismiss()
698     if not response: return
699
700     if response.get('which') == 'positive':
701         return modal_input('Private server', None)
702
703     i = response.get('item')
704     if i is not None:
705         response = plist.keys()[i]
706         return response
707
708
709 def seed_dialog():
710     if wallet.use_encryption:
711         password  = droid.dialogGetPassword('Seed').result
712         if not password: return
713     else:
714         password = None
715     
716     try:
717         seed = wallet.decode_seed(password)
718     except:
719         modal_dialog('error','incorrect password')
720         return
721
722     modal_dialog('Your seed is',seed)
723     modal_dialog('Mnemonic code:', ' '.join(mnemonic_encode(seed)) )
724
725 def change_password_dialog():
726     if wallet.use_encryption:
727         password  = droid.dialogGetPassword('Your wallet is encrypted').result
728         if password is None: return
729     else:
730         password = None
731
732     try:
733         seed = wallet.decode_seed(password)
734     except:
735         modal_dialog('error','incorrect password')
736         return
737
738     new_password  = droid.dialogGetPassword('Choose a password').result
739     if new_password == None:
740         return
741
742     if new_password != '':
743         password2  = droid.dialogGetPassword('Confirm new password').result
744         if new_password != password2:
745             modal_dialog('error','passwords do not match')
746             return
747
748     wallet.update_password(seed, password, new_password)
749     if new_password:
750         modal_dialog('Password updated','your wallet is encrypted')
751     else:
752         modal_dialog('No password','your wallet is not encrypted')
753     return True
754
755
756 def settings_loop():
757
758
759     def set_listview():
760         server, port, p = interface.server.split(':')
761         fee = str( Decimal( wallet.fee)/100000000 )
762         is_encrypted = 'yes' if wallet.use_encryption else 'no'
763         protocol = protocol_name(p)
764         droid.fullShow(settings_layout)
765         droid.fullSetList("myListView",['Server: ' + server, 'Protocol: '+ protocol, 'Port: '+port, 'Transaction fee: '+fee, 'Password: '+is_encrypted, 'Seed'])
766
767     set_listview()
768
769     out = None
770     while out is None:
771         event = droid.eventWait()
772         event = event.result
773         print "got event", event
774         if event == 'OK': continue
775         if not event: continue
776
777         plist, servers_list = interface.get_servers_list()
778         name = event.get("name")
779         if not name: continue
780
781         if name == "itemclick":
782             pos = event["data"]["position"]
783             host, port, protocol = interface.server.split(':')
784
785             if pos == "0": #server
786                 host = server_dialog(plist)
787                 if host:
788                     p = plist[host]
789                     port = p['t']
790                     srv = host + ':' + port + ':t'
791                     wallet.config.set_key("server", srv, True)
792                     try:
793                         wallet.interface.set_server(srv)
794                     except:
795                         modal_dialog('error','invalid server')
796                     set_listview()
797
798             elif pos == "1": #protocol
799                 if host in plist:
800                     srv = protocol_dialog(host, protocol, plist[host])
801                     if srv:
802                         wallet.config.set_key("server", srv, True)
803                         try:
804                             wallet.interface.set_server(srv)
805                         except:
806                             modal_dialog('error','invalid server')
807                         set_listview()
808
809             elif pos == "2": #port
810                 a_port = modal_input('Port number', 'If you use a public server, this field is set automatically when you set the protocol', port, "number")
811                 if a_port:
812                     if a_port != port:
813                         srv = host + ':' + a_port + ':'+ protocol
814                         wallet.config.set_key("server", srv, True)
815                         try:
816                             wallet.interface.set_server(srv)
817                         except:
818                             modal_dialog('error','invalid port number')
819                         set_listview()
820
821             elif pos == "3": #fee
822                 fee = modal_input('Transaction fee', 'The fee will be this amount multiplied by the number of inputs in your transaction. ', str( Decimal( wallet.fee)/100000000 ), "numberDecimal")
823                 if fee:
824                     try:
825                         fee = int( 100000000 * Decimal(fee) )
826                     except:
827                         modal_dialog('error','invalid fee value')
828                     if wallet.fee != fee:
829                         wallet.fee = fee
830                         wallet.save()
831                         set_listview()
832         
833             elif pos == "4":
834                 if change_password_dialog():
835                     set_listview()
836
837             elif pos == "5":
838                 seed_dialog()
839
840
841         elif name in menu_commands:
842             out = event["name"]
843
844         elif name == 'cancel':
845             out = 'main'
846
847         elif name == "key":
848             if event["data"]["key"] == '4':
849                 out = 'main'
850
851     return out
852
853 def add_menu(s):
854     droid.clearOptionsMenu()
855     if s == 'main':
856         droid.addOptionsMenuItem("Send","send",None,"")
857         droid.addOptionsMenuItem("Receive","receive",None,"")
858         droid.addOptionsMenuItem("Contacts","contacts",None,"")
859         droid.addOptionsMenuItem("Settings","settings",None,"")
860     elif s == 'receive':
861         droid.addOptionsMenuItem("Copy","clipboard",None,"")
862         droid.addOptionsMenuItem("Label","edit",None,"")
863     elif s == 'contacts':
864         droid.addOptionsMenuItem("Copy","clipboard",None,"")
865         droid.addOptionsMenuItem("Label","edit",None,"")
866         droid.addOptionsMenuItem("Pay to","paytocontact",None,"")
867         #droid.addOptionsMenuItem("Delete","deletecontact",None,"")
868
869
870 def make_bitmap(addr):
871     # fixme: this is highly inefficient
872     droid.dialogCreateSpinnerProgress("please wait")
873     droid.dialogShow()
874     try:
875         import pyqrnative, bmp
876         qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
877         qr.addData(addr)
878         qr.make()
879         k = qr.getModuleCount()
880         assert k == 33
881         bmp.save_qrcode(qr,"/sdcard/sl4a/qrcode.bmp")
882     finally:
883         droid.dialogDismiss()
884
885         
886
887
888 droid = android.Android()
889 menu_commands = ["send", "receive", "settings", "contacts", "main"]
890 wallet = None
891 interface = None
892
893 class ElectrumGui:
894
895     def __init__(self, w, config, app=None):
896         global wallet, interface
897         wallet = w
898         interface = wallet.interface
899         interface.register_callback('updated',update_callback)
900         interface.register_callback('connected', update_callback)
901         interface.register_callback('disconnected', update_callback)
902         interface.register_callback('disconnecting', update_callback)
903
904
905     def main(self, url):
906         s = 'main'
907         while True:
908             add_menu(s)
909             if s == 'main':
910                 droid.fullShow(main_layout())
911                 s = main_loop()
912
913             elif s == 'send':
914                 droid.fullShow(payto_layout)
915                 s = payto_loop()
916
917             elif s == 'receive':
918                 make_bitmap(receive_addr)
919                 droid.fullShow(qr_layout(receive_addr))
920                 s = receive_loop()
921
922             elif s == 'contacts':
923                 make_bitmap(contact_addr)
924                 droid.fullShow(qr_layout(contact_addr))
925                 s = contacts_loop()
926
927             elif s == 'settings':
928                 s = settings_loop()
929
930             else:
931                 break
932
933         droid.makeToast("Bye!")
934
935     def restore_or_create(self):
936         droid.dialogCreateAlert("Wallet not found","Do you want to create a new wallet, or restore an existing one?")
937         droid.dialogSetPositiveButtonText('Create')
938         droid.dialogSetNeutralButtonText('Restore')
939         droid.dialogSetNegativeButtonText('Cancel')
940         droid.dialogShow()
941         response = droid.dialogGetResponse().result
942         droid.dialogDismiss()
943         if not response: return
944         if response.get('which') == 'negative':
945             return
946
947         return 'restore' if response.get('which') == 'neutral' else 'create'
948
949     def seed_dialog(self):
950         if modal_question("Input method",None,'QR Code', 'mnemonic'):
951             code = droid.scanBarcode()
952             r = code.result
953             if r:
954                 seed = r['extras']['SCAN_RESULT']
955             else:
956                 return
957         else:
958             m = modal_input('Mnemonic','please enter your code')
959             try:
960                 seed = mnemonic_decode(m.split(' '))
961             except:
962                 modal_dialog('error: could not decode this seed')
963                 return
964
965         gap = 5   # default
966
967         return str(seed), gap
968
969
970     def network_dialog(self):
971         return True
972
973     def verify_seed(self):
974         self.wallet.save_seed()
975         return True
976         
977     def show_seed(self):
978         modal_dialog('Your seed is:', wallet.seed)
979         modal_dialog('Mnemonic code:', ' '.join(mnemonic_encode(wallet.seed)) )
980
981
982     def password_dialog(self):
983         change_password_dialog()
984
985
986     def restore_wallet(self):
987
988         msg = "recovering wallet..."
989         droid.dialogCreateSpinnerProgress("Electrum", msg)
990         droid.dialogShow()
991
992         wallet.update()
993
994         droid.dialogDismiss()
995         droid.vibrate()
996
997         if wallet.is_found():
998             wallet.fill_addressbook()
999             modal_dialog("recovery successful")
1000         else:
1001             if not modal_question("no transactions found for this seed","do you want to keep this wallet?"):
1002                 return False
1003
1004         wallet.save()
1005         return True
1006