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