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