simplify gui names
[electrum-nvc.git] / 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         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.add_contact(address)
495         else:
496             modal_dialog('Invalid address', data)
497
498
499 do_refresh = False
500
501 def update_callback():
502     global do_refresh
503     print "gui callback", wallet.interface.is_connected, wallet.up_to_date
504     do_refresh = True
505     droid.eventPost("refresh",'z')
506
507 def main_loop():
508     global do_refresh
509
510     update_layout()
511     out = None
512     quitting = False
513     while out is None:
514
515         event = droid.eventWait(1000).result
516         if event is None:
517             if do_refresh: 
518                 update_layout()
519                 do_refresh = False
520             continue
521
522         print "got event in main loop", repr(event)
523         if event == 'OK': continue
524         if event is None: continue
525         if not event.get("name"): continue
526
527         # request 2 taps before we exit
528         if event["name"]=="key":
529             if event["data"]["key"] == '4':
530                 if quitting:
531                     out = 'quit'
532                 else: 
533                     quitting = True
534         else: quitting = False
535
536         if event["name"]=="click":
537             id=event["data"]["id"]
538
539         elif event["name"]=="settings":
540             out = 'settings'
541
542         elif event["name"] in menu_commands:
543             out = event["name"]
544
545             if out == 'contacts':
546                 global contact_addr
547                 contact_addr = select_from_contacts()
548                 if contact_addr == 'newcontact':
549                     make_new_contact()
550                     contact_addr = None
551                 if not contact_addr:
552                     out = None
553
554             elif out == "receive":
555                 global receive_addr
556                 receive_addr = select_from_addresses()
557                 if receive_addr:
558                     amount = modal_input('Amount', 'Amount you want receive. ', '', "numberDecimal")
559                     if amount:
560                         receive_addr = 'bitcoin:%s?amount=%s'%(receive_addr, amount)
561
562                 if not receive_addr:
563                     out = None
564
565
566     return out
567                     
568
569 def payto_loop():
570     global recipient
571     if recipient:
572         droid.fullSetProperty("recipient","text",recipient)
573         recipient = None
574
575     out = None
576     while out is None:
577         event = droid.eventWait().result
578         if not event: continue
579         print "got event in payto loop", event
580         if event == 'OK': continue
581         if not event.get("name"): continue
582
583         if event["name"] == "click":
584             id = event["data"]["id"]
585
586             if id=="buttonPay":
587
588                 droid.fullQuery()
589                 recipient = droid.fullQueryDetail("recipient").result.get('text')
590                 label  = droid.fullQueryDetail("label").result.get('text')
591                 amount = droid.fullQueryDetail('amount').result.get('text')
592
593                 if not is_valid(recipient):
594                     modal_dialog('Error','Invalid Bitcoin address')
595                     continue
596
597                 try:
598                     amount = int( 100000000 * Decimal(amount) )
599                 except:
600                     modal_dialog('Error','Invalid amount')
601                     continue
602
603                 result = pay_to(recipient, amount, wallet.fee, label)
604                 if result:
605                     out = 'main'
606
607             elif id=="buttonContacts":
608                 addr = select_from_contacts()
609                 droid.fullSetProperty("recipient","text",addr)
610
611             elif id=="buttonQR":
612                 code = droid.scanBarcode()
613                 r = code.result
614                 if r:
615                     data = r['extras']['SCAN_RESULT']
616                     if data:
617                         if re.match('^bitcoin:', data):
618                             payto, amount, label, _, _, _, _ = util.parse_url(data)
619                             droid.fullSetProperty("recipient", "text",payto)
620                             droid.fullSetProperty("amount", "text", amount)
621                             droid.fullSetProperty("label", "text", label)
622                         else:
623                             droid.fullSetProperty("recipient", "text", data)
624
625                     
626         elif event["name"] in menu_commands:
627             out = event["name"]
628
629         elif event["name"]=="key":
630             if event["data"]["key"] == '4':
631                 out = 'main'
632
633         #elif event["name"]=="screen":
634         #    if event["data"]=="destroy":
635         #        out = 'main'
636
637     return out
638
639
640 receive_addr = ''
641 contact_addr = ''
642 recipient = ''
643
644 def receive_loop():
645     out = None
646     while out is None:
647         event = droid.eventWait().result
648         print "got event", event
649         if event["name"]=="key":
650             if event["data"]["key"] == '4':
651                 out = 'main'
652
653         elif event["name"]=="clipboard":
654             droid.setClipboard(receive_addr)
655             modal_dialog('Address copied to clipboard',receive_addr)
656
657         elif event["name"]=="edit":
658             edit_label(receive_addr)
659
660     return out
661
662 def contacts_loop():
663     global recipient
664     out = None
665     while out is None:
666         event = droid.eventWait().result
667         print "got event", event
668         if event["name"]=="key":
669             if event["data"]["key"] == '4':
670                 out = 'main'
671
672         elif event["name"]=="clipboard":
673             droid.setClipboard(contact_addr)
674             modal_dialog('Address copied to clipboard',contact_addr)
675
676         elif event["name"]=="edit":
677             edit_label(contact_addr)
678
679         elif event["name"]=="paytocontact":
680             recipient = contact_addr
681             out = 'send'
682
683         elif event["name"]=="deletecontact":
684             if modal_question('delete contact', contact_addr):
685                 out = 'main'
686
687     return out
688
689
690 def server_dialog(servers):
691     droid.dialogCreateAlert("Public servers")
692     droid.dialogSetItems( servers.keys() )
693     droid.dialogSetPositiveButtonText('Private server')
694     droid.dialogShow()
695     response = droid.dialogGetResponse().result
696     droid.dialogDismiss()
697     if not response: return
698
699     if response.get('which') == 'positive':
700         return modal_input('Private server', None)
701
702     i = response.get('item')
703     if i is not None:
704         response = servers.keys()[i]
705         return response
706
707
708 def seed_dialog():
709     if wallet.use_encryption:
710         password  = droid.dialogGetPassword('Seed').result
711         if not password: return
712     else:
713         password = None
714     
715     try:
716         seed = wallet.decode_seed(password)
717     except:
718         modal_dialog('error','incorrect password')
719         return
720
721     modal_dialog('Your seed is',seed)
722     modal_dialog('Mnemonic code:', ' '.join(mnemonic_encode(seed)) )
723
724 def change_password_dialog():
725     if wallet.use_encryption:
726         password  = droid.dialogGetPassword('Your wallet is encrypted').result
727         if password is None: return
728     else:
729         password = None
730
731     try:
732         seed = wallet.decode_seed(password)
733     except:
734         modal_dialog('error','incorrect password')
735         return
736
737     new_password  = droid.dialogGetPassword('Choose a password').result
738     if new_password == None:
739         return
740
741     if new_password != '':
742         password2  = droid.dialogGetPassword('Confirm new password').result
743         if new_password != password2:
744             modal_dialog('error','passwords do not match')
745             return
746
747     wallet.update_password(seed, password, new_password)
748     if new_password:
749         modal_dialog('Password updated','your wallet is encrypted')
750     else:
751         modal_dialog('No password','your wallet is not encrypted')
752     return True
753
754
755 def settings_loop():
756
757
758     def set_listview():
759         server, port, p = interface.server.split(':')
760         fee = str( Decimal( wallet.fee)/100000000 )
761         is_encrypted = 'yes' if wallet.use_encryption else 'no'
762         protocol = protocol_name(p)
763         droid.fullShow(settings_layout)
764         droid.fullSetList("myListView",['Server: ' + server, 'Protocol: '+ protocol, 'Port: '+port, 'Transaction fee: '+fee, 'Password: '+is_encrypted, 'Seed'])
765
766     set_listview()
767
768     out = None
769     while out is None:
770         event = droid.eventWait()
771         event = event.result
772         print "got event", event
773         if event == 'OK': continue
774         if not event: continue
775
776         servers = interface.get_servers()
777         name = event.get("name")
778         if not name: continue
779
780         if name == "itemclick":
781             pos = event["data"]["position"]
782             host, port, protocol = interface.server.split(':')
783
784             if pos == "0": #server
785                 host = server_dialog(servers)
786                 if host:
787                     p = servers[host]
788                     port = p['t']
789                     srv = host + ':' + port + ':t'
790                     wallet.config.set_key("server", srv, True)
791                     try:
792                         wallet.interface.set_server(srv)
793                     except:
794                         modal_dialog('error','invalid server')
795                     set_listview()
796
797             elif pos == "1": #protocol
798                 if host in servers:
799                     srv = protocol_dialog(host, protocol, servers[host])
800                     if srv:
801                         wallet.config.set_key("server", srv, True)
802                         try:
803                             wallet.interface.set_server(srv)
804                         except:
805                             modal_dialog('error','invalid server')
806                         set_listview()
807
808             elif pos == "2": #port
809                 a_port = modal_input('Port number', 'If you use a public server, this field is set automatically when you set the protocol', port, "number")
810                 if a_port:
811                     if a_port != port:
812                         srv = host + ':' + a_port + ':'+ protocol
813                         wallet.config.set_key("server", srv, True)
814                         try:
815                             wallet.interface.set_server(srv)
816                         except:
817                             modal_dialog('error','invalid port number')
818                         set_listview()
819
820             elif pos == "3": #fee
821                 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")
822                 if fee:
823                     try:
824                         fee = int( 100000000 * Decimal(fee) )
825                     except:
826                         modal_dialog('error','invalid fee value')
827                     wallet.set_fee(fee)
828                     set_listview()
829
830             elif pos == "4":
831                 if change_password_dialog():
832                     set_listview()
833
834             elif pos == "5":
835                 seed_dialog()
836
837
838         elif name in menu_commands:
839             out = event["name"]
840
841         elif name == 'cancel':
842             out = 'main'
843
844         elif name == "key":
845             if event["data"]["key"] == '4':
846                 out = 'main'
847
848     return out
849
850 def add_menu(s):
851     droid.clearOptionsMenu()
852     if s == 'main':
853         droid.addOptionsMenuItem("Send","send",None,"")
854         droid.addOptionsMenuItem("Receive","receive",None,"")
855         droid.addOptionsMenuItem("Contacts","contacts",None,"")
856         droid.addOptionsMenuItem("Settings","settings",None,"")
857     elif s == 'receive':
858         droid.addOptionsMenuItem("Copy","clipboard",None,"")
859         droid.addOptionsMenuItem("Label","edit",None,"")
860     elif s == 'contacts':
861         droid.addOptionsMenuItem("Copy","clipboard",None,"")
862         droid.addOptionsMenuItem("Label","edit",None,"")
863         droid.addOptionsMenuItem("Pay to","paytocontact",None,"")
864         #droid.addOptionsMenuItem("Delete","deletecontact",None,"")
865
866
867 def make_bitmap(addr):
868     # fixme: this is highly inefficient
869     droid.dialogCreateSpinnerProgress("please wait")
870     droid.dialogShow()
871     try:
872         import pyqrnative, bmp
873         qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
874         qr.addData(addr)
875         qr.make()
876         k = qr.getModuleCount()
877         assert k == 33
878         bmp.save_qrcode(qr,"/sdcard/sl4a/qrcode.bmp")
879     finally:
880         droid.dialogDismiss()
881
882         
883
884
885 droid = android.Android()
886 menu_commands = ["send", "receive", "settings", "contacts", "main"]
887 wallet = None
888 interface = None
889
890 class ElectrumGui:
891
892     def __init__(self, w, config, app=None):
893         global wallet, interface
894         wallet = w
895         interface = wallet.interface
896         interface.register_callback('updated',update_callback)
897         interface.register_callback('connected', update_callback)
898         interface.register_callback('disconnected', update_callback)
899         interface.register_callback('disconnecting', update_callback)
900
901
902     def main(self, url):
903         s = 'main'
904         while True:
905             add_menu(s)
906             if s == 'main':
907                 droid.fullShow(main_layout())
908                 s = main_loop()
909
910             elif s == 'send':
911                 droid.fullShow(payto_layout)
912                 s = payto_loop()
913
914             elif s == 'receive':
915                 make_bitmap(receive_addr)
916                 droid.fullShow(qr_layout(receive_addr))
917                 s = receive_loop()
918
919             elif s == 'contacts':
920                 make_bitmap(contact_addr)
921                 droid.fullShow(qr_layout(contact_addr))
922                 s = contacts_loop()
923
924             elif s == 'settings':
925                 s = settings_loop()
926
927             else:
928                 break
929
930         droid.makeToast("Bye!")
931
932     def restore_or_create(self):
933         droid.dialogCreateAlert("Wallet not found","Do you want to create a new wallet, or restore an existing one?")
934         droid.dialogSetPositiveButtonText('Create')
935         droid.dialogSetNeutralButtonText('Restore')
936         droid.dialogSetNegativeButtonText('Cancel')
937         droid.dialogShow()
938         response = droid.dialogGetResponse().result
939         droid.dialogDismiss()
940         if not response: return
941         if response.get('which') == 'negative':
942             return
943
944         return 'restore' if response.get('which') == 'neutral' else 'create'
945
946     def seed_dialog(self):
947         if modal_question("Input method",None,'QR Code', 'mnemonic'):
948             code = droid.scanBarcode()
949             r = code.result
950             if r:
951                 seed = r['extras']['SCAN_RESULT']
952             else:
953                 return
954         else:
955             m = modal_input('Mnemonic','please enter your code')
956             try:
957                 seed = mnemonic_decode(m.split(' '))
958             except:
959                 modal_dialog('error: could not decode this seed')
960                 return
961
962         gap = 5   # default
963
964         return str(seed), gap
965
966
967     def network_dialog(self):
968         return True
969
970     def verify_seed(self):
971         wallet.save_seed()
972         return True
973         
974     def show_seed(self):
975         modal_dialog('Your seed is:', wallet.seed)
976         modal_dialog('Mnemonic code:', ' '.join(mnemonic_encode(wallet.seed)) )
977
978
979     def password_dialog(self):
980         change_password_dialog()
981
982
983     def restore_wallet(self):
984
985         msg = "recovering wallet..."
986         droid.dialogCreateSpinnerProgress("Electrum", msg)
987         droid.dialogShow()
988
989         wallet.update()
990
991         droid.dialogDismiss()
992         droid.vibrate()
993
994         if wallet.is_found():
995             wallet.fill_addressbook()
996             modal_dialog("recovery successful")
997         else:
998             if not modal_question("no transactions found for this seed","do you want to keep this wallet?"):
999                 return False
1000
1001         wallet.save()
1002         return True
1003