fix
[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 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     addresses = wallet.addresses()
104     for i in range(len(addresses)):
105         addr = addresses[i]
106         label = wallet.labels.get(addr,addr)
107         l.append( label )
108     droid.dialogSetItems(l)
109     droid.dialogShow()
110     response = droid.dialogGetResponse()
111     result = response.result.get('item')
112     droid.dialogDismiss()
113     if result is not None:
114         addr = addresses[result]
115         return addr
116
117
118 def protocol_name(p):
119     if p == 't': return 'TCP'
120     if p == 'h': return 'HTTP'
121     if p == 's': return 'TCP/SSL'
122     if p == 'g': return 'HTTPS'
123
124
125 def protocol_dialog(host, protocol, z):
126     droid.dialogCreateAlert('Protocol',host)
127     if z:
128         protocols = z.keys()
129     else:
130         protocols = 'thsg'
131     l = []
132     current = protocols.index(protocol)
133     for p in protocols:
134         l.append(protocol_name(p))
135     droid.dialogSetSingleChoiceItems(l, current)
136     droid.dialogSetPositiveButtonText('OK')
137     droid.dialogSetNegativeButtonText('Cancel')
138     droid.dialogShow()
139     response = droid.dialogGetResponse().result
140     selected_item = droid.dialogGetSelectedItems().result
141     droid.dialogDismiss()
142
143     if not response: return
144     if not selected_item: return
145     if response.get('which') == 'positive':
146         p = protocols[selected_item[0]]
147         port = z[p]
148         return host + ':' + port + ':' + p
149
150
151
152
153 def make_layout(s, scrollable = False):
154     content = """
155
156       <LinearLayout 
157         android:id="@+id/zz"
158         android:layout_width="match_parent"
159         android:layout_height="wrap_content" 
160         android:background="#ff222222">
161
162         <TextView
163           android:id="@+id/textElectrum"
164           android:text="Electrum"
165           android:textSize="7pt"
166           android:textColor="#ff4444ff"
167           android:gravity="left"
168           android:layout_height="wrap_content"
169           android:layout_width="match_parent"
170         />
171       </LinearLayout>
172
173         %s   """%s
174
175     if scrollable:
176         content = """
177       <ScrollView 
178         android:id="@+id/scrollview"
179         android:layout_width="match_parent"
180         android:layout_height="match_parent" >
181
182       <LinearLayout
183         android:orientation="vertical" 
184         android:layout_width="match_parent"
185         android:layout_height="wrap_content" >
186
187       %s
188
189       </LinearLayout>
190       </ScrollView>
191       """%content
192
193
194     return """<?xml version="1.0" encoding="utf-8"?>
195       <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
196         android:id="@+id/background"
197         android:orientation="vertical" 
198         android:layout_width="match_parent"
199         android:layout_height="match_parent" 
200         android:background="#ff000022">
201
202       %s 
203       </LinearLayout>"""%content
204
205
206
207
208 def main_layout():
209     return make_layout("""
210         <TextView android:id="@+id/balanceTextView" 
211                 android:layout_width="match_parent"
212                 android:text=""
213                 android:textColor="#ffffffff"
214                 android:textAppearance="?android:attr/textAppearanceLarge" 
215                 android:padding="7dip"
216                 android:textSize="8pt"
217                 android:gravity="center_vertical|center_horizontal|left">
218         </TextView>
219
220         <TextView android:id="@+id/historyTextView" 
221                 android:layout_width="match_parent"
222                 android:layout_height="wrap_content" 
223                 android:text="Recent transactions"
224                 android:textAppearance="?android:attr/textAppearanceLarge" 
225                 android:gravity="center_vertical|center_horizontal|center">
226         </TextView>
227
228         %s """%get_history_layout(15),True)
229
230
231
232 def qr_layout(addr):
233     return make_layout("""
234
235      <TextView android:id="@+id/addrTextView" 
236                 android:layout_width="match_parent"
237                 android:layout_height="50" 
238                 android:text="%s"
239                 android:textAppearance="?android:attr/textAppearanceLarge" 
240                 android:gravity="center_vertical|center_horizontal|center">
241      </TextView>
242
243      <ImageView
244         android:id="@+id/qrView"
245         android:gravity="center"
246         android:layout_width="match_parent"
247         android:layout_height="350"
248         android:antialias="false"
249         android:src="file:///sdcard/sl4a/qrcode.bmp" /> 
250
251      <TextView android:id="@+id/labelTextView" 
252                 android:layout_width="match_parent"
253                 android:layout_height="50" 
254                 android:text="%s"
255                 android:textAppearance="?android:attr/textAppearanceLarge" 
256                 android:gravity="center_vertical|center_horizontal|center">
257      </TextView>
258
259      """%(addr,wallet.labels.get(addr,'')), True)
260
261 payto_layout = make_layout("""
262
263         <TextView android:id="@+id/recipientTextView" 
264                 android:layout_width="match_parent"
265                 android:layout_height="wrap_content" 
266                 android:text="Pay to:"
267                 android:textAppearance="?android:attr/textAppearanceLarge" 
268                 android:gravity="left">
269         </TextView>
270
271
272         <EditText android:id="@+id/recipient"
273                 android:layout_width="match_parent"
274                 android:layout_height="wrap_content" 
275                 android:tag="Tag Me" android:inputType="text">
276         </EditText>
277
278         <LinearLayout android:id="@+id/linearLayout1"
279                 android:layout_width="match_parent"
280                 android:layout_height="wrap_content">
281                 <Button android:id="@+id/buttonQR" android:layout_width="wrap_content"
282                         android:layout_height="wrap_content" android:text="From QR code"></Button>
283                 <Button android:id="@+id/buttonContacts" android:layout_width="wrap_content"
284                         android:layout_height="wrap_content" android:text="From Contacts"></Button>
285         </LinearLayout>
286
287
288         <TextView android:id="@+id/labelTextView" 
289                 android:layout_width="match_parent"
290                 android:layout_height="wrap_content" 
291                 android:text="Description:"
292                 android:textAppearance="?android:attr/textAppearanceLarge" 
293                 android:gravity="left">
294         </TextView>
295
296         <EditText android:id="@+id/label"
297                 android:layout_width="match_parent"
298                 android:layout_height="wrap_content" 
299                 android:tag="Tag Me" android:inputType="text">
300         </EditText>
301
302         <TextView android:id="@+id/amountLabelTextView" 
303                 android:layout_width="match_parent"
304                 android:layout_height="wrap_content" 
305                 android:text="Amount:"
306                 android:textAppearance="?android:attr/textAppearanceLarge" 
307                 android:gravity="left">
308         </TextView>
309
310         <EditText android:id="@+id/amount"
311                 android:layout_width="match_parent"
312                 android:layout_height="wrap_content" 
313                 android:tag="Tag Me" android:inputType="numberDecimal">
314         </EditText>
315
316         <LinearLayout android:layout_width="match_parent"
317                 android:layout_height="wrap_content" android:id="@+id/linearLayout1">
318                 <Button android:id="@+id/buttonPay" android:layout_width="wrap_content"
319                         android:layout_height="wrap_content" android:text="Send"></Button>
320         </LinearLayout>""",False)
321
322
323
324 settings_layout = make_layout(""" <ListView
325            android:id="@+id/myListView" 
326            android:layout_width="match_parent"
327            android:layout_height="wrap_content" />""")
328
329
330
331 def get_history_values(n):
332     values = []
333     h = wallet.get_tx_history()
334     length = min(n, len(h))
335     for i in range(length):
336         tx_hash, conf, is_mine, value, fee, balance, timestamp = h[-i-1]
337         try:
338             dt = datetime.datetime.fromtimestamp( timestamp )
339             if dt.date() == dt.today().date():
340                 time_str = str( dt.time() )
341             else:
342                 time_str = str( dt.date() )
343         except:
344             time_str = 'pending'
345
346         conf_str = 'v' if conf else 'o'
347         label, is_default_label = wallet.get_label(tx_hash)
348         values.append((conf_str, '  ' + time_str, '  ' + format_satoshis(value,True), '  ' + label ))
349
350     return values
351
352
353 def get_history_layout(n):
354     rows = ""
355     i = 0
356     values = get_history_values(n)
357     for v in values:
358         a,b,c,d = v
359         color = "#ff00ff00" if a == 'v' else "#ffff0000"
360         rows += """
361         <TableRow>
362           <TextView
363             android:id="@+id/hl_%d_col1" 
364             android:layout_column="0"
365             android:text="%s"
366             android:textColor="%s"
367             android:padding="3" />
368           <TextView
369             android:id="@+id/hl_%d_col2" 
370             android:layout_column="1"
371             android:text="%s"
372             android:padding="3" />
373           <TextView
374             android:id="@+id/hl_%d_col3" 
375             android:layout_column="2"
376             android:text="%s"
377             android:padding="3" />
378           <TextView
379             android:id="@+id/hl_%d_col4" 
380             android:layout_column="3"
381             android:text="%s"
382             android:padding="4" />
383         </TableRow>"""%(i,a,color,i,b,i,c,i,d)
384         i += 1
385
386     output = """
387 <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
388     android:layout_width="fill_parent"
389     android:layout_height="wrap_content"
390     android:stretchColumns="0,1,2,3">
391     %s
392 </TableLayout>"""% rows
393     return output
394
395
396 def set_history_layout(n):
397     values = get_history_values(n)
398     i = 0
399     for v in values:
400         a,b,c,d = v
401         droid.fullSetProperty("hl_%d_col1"%i,"text", a)
402
403         if a == 'v':
404             droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ff00ff00")
405         else:
406             droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ffff0000")
407
408         droid.fullSetProperty("hl_%d_col2"%i,"text", b)
409         droid.fullSetProperty("hl_%d_col3"%i,"text", c)
410         droid.fullSetProperty("hl_%d_col4"%i,"text", d)
411         i += 1
412
413
414
415
416 status_text = ''
417 def update_layout():
418     global status_text
419     if not wallet.interface.is_connected:
420         text = "Not connected..."
421     elif not wallet.up_to_date:
422         text = "Synchronizing..."
423     else:
424         c, u = wallet.get_balance()
425         text = "Balance:"+format_satoshis(c) 
426         if u : text += '   [' + format_satoshis(u,True).strip() + ']'
427
428
429     # vibrate if status changed
430     if text != status_text:
431         if status_text and wallet.interface.is_connected and wallet.up_to_date:
432             droid.vibrate()
433         status_text = text
434
435     droid.fullSetProperty("balanceTextView", "text", status_text)
436
437     if wallet.up_to_date:
438         set_history_layout(15)
439
440
441
442
443 def pay_to(recipient, amount, fee, label):
444
445     if wallet.use_encryption:
446         password  = droid.dialogGetPassword('Password').result
447         if not password: return
448     else:
449         password = None
450
451     droid.dialogCreateSpinnerProgress("Electrum", "signing transaction...")
452     droid.dialogShow()
453
454     try:
455         tx = wallet.mktx( [(recipient, amount)], password, fee)
456     except BaseException, e:
457         modal_dialog('error', e.message)
458         droid.dialogDismiss()
459         return
460
461     if label: 
462         self.wallet.labels[tx.hash()] = label
463
464     droid.dialogDismiss()
465
466     r, h = wallet.sendtx( tx )
467     if r:
468         modal_dialog('Payment sent', h)
469         return True
470     else:
471         modal_dialog('Error', h)
472
473
474
475
476
477
478
479 def make_new_contact():
480     code = droid.scanBarcode()
481     r = code.result
482     if r:
483         data = r['extras']['SCAN_RESULT']
484         if data:
485             if re.match('^bitcoin:', data):
486                 address, _, _, _, _, _, _ = wallet.parse_url(data, None, lambda x: modal_question('Question',x))
487             elif is_valid(data):
488                 address = data
489             else:
490                 address = None
491             if address:
492                 if modal_question('Add to contacts?', address):
493                     wallet.addressbook.append(address)
494                     wallet.save()
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, _, _, _, _ = wallet.parse_url(data, None, lambda x: modal_question('Question', x))
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(plist):
691     droid.dialogCreateAlert("Public servers")
692     droid.dialogSetItems( plist.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 = plist.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         plist, servers_list = interface.get_servers_list()
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(plist)
786                 if host:
787                     p = plist[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 plist:
799                     srv = protocol_dialog(host, protocol, plist[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                     if wallet.fee != fee:
828                         wallet.fee = fee
829                         wallet.save()
830                         set_listview()
831         
832             elif pos == "4":
833                 if change_password_dialog():
834                     set_listview()
835
836             elif pos == "5":
837                 seed_dialog()
838
839
840         elif name in menu_commands:
841             out = event["name"]
842
843         elif name == 'cancel':
844             out = 'main'
845
846         elif name == "key":
847             if event["data"]["key"] == '4':
848                 out = 'main'
849
850     return out
851
852 def add_menu(s):
853     droid.clearOptionsMenu()
854     if s == 'main':
855         droid.addOptionsMenuItem("Send","send",None,"")
856         droid.addOptionsMenuItem("Receive","receive",None,"")
857         droid.addOptionsMenuItem("Contacts","contacts",None,"")
858         droid.addOptionsMenuItem("Settings","settings",None,"")
859     elif s == 'receive':
860         droid.addOptionsMenuItem("Copy","clipboard",None,"")
861         droid.addOptionsMenuItem("Label","edit",None,"")
862     elif s == 'contacts':
863         droid.addOptionsMenuItem("Copy","clipboard",None,"")
864         droid.addOptionsMenuItem("Label","edit",None,"")
865         droid.addOptionsMenuItem("Pay to","paytocontact",None,"")
866         #droid.addOptionsMenuItem("Delete","deletecontact",None,"")
867
868
869 def make_bitmap(addr):
870     # fixme: this is highly inefficient
871     droid.dialogCreateSpinnerProgress("please wait")
872     droid.dialogShow()
873     try:
874         import pyqrnative, bmp
875         qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
876         qr.addData(addr)
877         qr.make()
878         k = qr.getModuleCount()
879         assert k == 33
880         bmp.save_qrcode(qr,"/sdcard/sl4a/qrcode.bmp")
881     finally:
882         droid.dialogDismiss()
883
884         
885
886
887 droid = android.Android()
888 menu_commands = ["send", "receive", "settings", "contacts", "main"]
889 wallet = None
890 interface = None
891
892 class ElectrumGui:
893
894     def __init__(self, w, config, app=None):
895         global wallet, interface
896         wallet = w
897         interface = wallet.interface
898         interface.register_callback('updated',update_callback)
899         interface.register_callback('connected', update_callback)
900         interface.register_callback('disconnected', update_callback)
901         interface.register_callback('disconnecting', update_callback)
902
903
904     def main(self, url):
905         s = 'main'
906         while True:
907             add_menu(s)
908             if s == 'main':
909                 droid.fullShow(main_layout())
910                 s = main_loop()
911
912             elif s == 'send':
913                 droid.fullShow(payto_layout)
914                 s = payto_loop()
915
916             elif s == 'receive':
917                 make_bitmap(receive_addr)
918                 droid.fullShow(qr_layout(receive_addr))
919                 s = receive_loop()
920
921             elif s == 'contacts':
922                 make_bitmap(contact_addr)
923                 droid.fullShow(qr_layout(contact_addr))
924                 s = contacts_loop()
925
926             elif s == 'settings':
927                 s = settings_loop()
928
929             else:
930                 break
931
932         droid.makeToast("Bye!")
933
934     def restore_or_create(self):
935         droid.dialogCreateAlert("Wallet not found","Do you want to create a new wallet, or restore an existing one?")
936         droid.dialogSetPositiveButtonText('Create')
937         droid.dialogSetNeutralButtonText('Restore')
938         droid.dialogSetNegativeButtonText('Cancel')
939         droid.dialogShow()
940         response = droid.dialogGetResponse().result
941         droid.dialogDismiss()
942         if not response: return
943         if response.get('which') == 'negative':
944             return
945
946         return 'restore' if response.get('which') == 'neutral' else 'create'
947
948     def seed_dialog(self):
949         if modal_question("Input method",None,'QR Code', 'mnemonic'):
950             code = droid.scanBarcode()
951             r = code.result
952             if r:
953                 seed = r['extras']['SCAN_RESULT']
954             else:
955                 return
956         else:
957             m = modal_input('Mnemonic','please enter your code')
958             try:
959                 seed = mnemonic_decode(m.split(' '))
960             except:
961                 modal_dialog('error: could not decode this seed')
962                 return
963
964         gap = 5   # default
965
966         return str(seed), gap
967
968
969     def network_dialog(self):
970         return True
971
972         
973     def show_seed(self):
974         modal_dialog('Your seed is:', wallet.seed)
975         modal_dialog('Mnemonic code:', ' '.join(mnemonic_encode(wallet.seed)) )
976
977
978     def password_dialog(self):
979         change_password_dialog()
980
981
982     def restore_wallet(self):
983
984         msg = "recovering wallet..."
985         droid.dialogCreateSpinnerProgress("Electrum", msg)
986         droid.dialogShow()
987
988         wallet.update()
989
990         droid.dialogDismiss()
991         droid.vibrate()
992
993         if wallet.is_found():
994             wallet.fill_addressbook()
995             modal_dialog("recovery successful")
996         else:
997             if not modal_question("no transactions found for this seed","do you want to keep this wallet?"):
998                 return False
999
1000         wallet.save()
1001         return True
1002