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