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