commit c121c1aa4e2d20825d4eafc201144e84df3129dd
parent 9938316400ad263ab9bec40e93f7e0b10c2ed9cd
Author: akshayaurora <akshayaurora@gmail.com>
Date: Thu, 5 Jun 2014 06:12:29 +0530
reorganize files and bring code inline with current master
Conflicts:
lib/simple_config.py
Diffstat:
69 files changed, 5738 insertions(+), 2781 deletions(-)
diff --git a/data/fonts/Roboto-Bold.ttf b/data/fonts/Roboto-Bold.ttf
Binary files differ.
diff --git a/data/fonts/Roboto-Condensed.ttf b/data/fonts/Roboto-Condensed.ttf
Binary files differ.
diff --git a/data/fonts/Roboto-Medium.ttf b/data/fonts/Roboto-Medium.ttf
Binary files differ.
diff --git a/gui/kivy/Makefile b/gui/kivy/Makefile
@@ -9,7 +9,7 @@ apk:
# running pre build setup
@cp tools/buildozer.spec ../../buildozer.spec
# get aes.py
- @cd ../..; wget -4 https://raw.github.com/devrandom/slowaes/master/python/aes.py
+ @cd ../..; curl -O https://raw.github.com/devrandom/slowaes/master/python/aes.py
# rename electrum to main.py
@mv ../../electrum ../../main.py
@-if [ ! -d "../../.buildozer" ];then \
diff --git a/gui/kivy/__init__.py b/gui/kivy/__init__.py
@@ -21,7 +21,7 @@
import sys
#, time, datetime, re, threading
#from electrum.i18n import _, set_language
-#from electrum.util import print_error, print_msg, parse_url
+from electrum.util import print_error, print_msg, parse_url
#:TODO: replace this with kivy's own plugin managment
#from electrum.plugins import run_hook
@@ -42,9 +42,8 @@ from kivy.logger import Logger
from electrum.bitcoin import MIN_RELAY_TX_FEE
-#:TODO main window
from main_window import ElectrumWindow
-from electrum.plugins import init_plugins
+#from electrum.plugins import init_plugins
#:TODO find a equivalent method to register to `bitcoin:` uri
#: ref: http://stackoverflow.com/questions/30931/register-file-extensions-mime-types-in-linux
@@ -60,7 +59,6 @@ from electrum.plugins import init_plugins
# return True
# return False
-
class ElectrumGui:
def __init__(self, config, network, app=None):
@@ -74,6 +72,47 @@ class ElectrumGui:
# base
#init_plugins(self)
+ def set_url(self, url):
+ from electrum import util
+ from decimal import Decimal
+
+ try:
+ address, amount, label, message,\
+ request_url, url = util.parse_url(url)
+ except Exception:
+ self.main_window.show_error(_('Invalid bitcoin URL'))
+ return
+
+ if amount:
+ try:
+ if main_window.base_unit == 'mBTC':
+ amount = str( 1000* Decimal(amount))
+ else:
+ amount = str(Decimal(amount))
+ except Exception:
+ amount = "0.0"
+ self.main_window.show_error(_('Invalid Amount'))
+
+ if request_url:
+ try:
+ from electrum import paymentrequest
+ except:
+ self.main_window.show_error("cannot import payment request")
+ request_url = None
+
+ if not request_url:
+ self.main_window.set_send(address, amount, label, message)
+ return
+
+ def payment_request():
+ self.payment_request = paymentrequest.PaymentRequest(request_url)
+ if self.payment_request.verify():
+ Clock.schedule_once(self.main_window.payment_request_ok)
+ else:
+ Clock.schedule_once(self.main_window.payment_request_error)
+
+ threading.Thread(target=payment_request).start()
+ self.main_window.prepare_for_payment_request()
def main(self, url):
''' The main entry point of the kivy ux
@@ -83,5 +122,7 @@ class ElectrumGui:
'''
self.main_window = w = ElectrumWindow(config=self.config,
- network=self.network)
- w.run()
+ network=self.network,
+ url=url,
+ gui_object=self)
+ w.run()+
\ No newline at end of file
diff --git a/gui/kivy/carousel.py b/gui/kivy/carousel.py
@@ -1,32 +0,0 @@
-from kivy.uix.carousel import Carousel
-from kivy.clock import Clock
-
-class Carousel(Carousel):
-
- def on_touch_move(self, touch):
- if self._get_uid('cavoid') in touch.ud:
- return
- if self._touch is not touch:
- super(Carousel, self).on_touch_move(touch)
- return self._get_uid() in touch.ud
- if touch.grab_current is not self:
- return True
- ud = touch.ud[self._get_uid()]
- direction = self.direction
- if ud['mode'] == 'unknown':
- if direction[0] in ('r', 'l'):
- distance = abs(touch.ox - touch.x)
- else:
- distance = abs(touch.oy - touch.y)
- if distance > self.scroll_distance:
- Clock.unschedule(self._change_touch_mode)
- ud['mode'] = 'scroll'
- else:
- diff = 0
- if direction[0] in ('r', 'l'):
- diff = touch.dx
- if direction[0] in ('t', 'b'):
- diff = touch.dy
-
- self._offset += diff * 1.27
- return True-
\ No newline at end of file
diff --git a/gui/kivy/dialog.py b/gui/kivy/dialog.py
@@ -1,686 +0,0 @@
-from functools import partial
-
-from kivy.app import App
-from kivy.factory import Factory
-from kivy.uix.button import Button
-from kivy.uix.bubble import Bubble
-from kivy.uix.popup import Popup
-from kivy.uix.widget import Widget
-from kivy.uix.carousel import Carousel
-from kivy.uix.tabbedpanel import TabbedPanelHeader
-from kivy.properties import (NumericProperty, StringProperty, ListProperty,
- ObjectProperty, AliasProperty, OptionProperty,
- BooleanProperty)
-
-from kivy.animation import Animation
-from kivy.core.window import Window
-from kivy.clock import Clock
-from kivy.lang import Builder
-from kivy.metrics import dp, inch
-
-#from electrum.bitcoin import is_valid
-from electrum.i18n import _
-
-# Delayed inits
-QRScanner = None
-NFCSCanner = None
-ScreenAddress = None
-decode_uri = None
-
-DEFAULT_PATH = '/tmp/'
-app = App.get_running_app()
-
-class CarouselHeader(TabbedPanelHeader):
-
- slide = NumericProperty(0)
- ''' indicates the link to carousels slide'''
-
-class AnimatedPopup(Popup):
-
- def open(self):
- self.opacity = 0
- super(AnimatedPopup, self).open()
- anim = Animation(opacity=1, d=.5).start(self)
-
- def dismiss(self):
- def on_complete(*l):
- super(AnimatedPopup, self).dismiss()
- anim = Animation(opacity=0, d=.5)
- anim.bind(on_complete=on_complete)
- anim.start(self)
-
-
-class CarouselDialog(AnimatedPopup):
- ''' A Popup dialog with a CarouselIndicator used as the content.
- '''
-
- carousel_content = ObjectProperty(None)
-
- def open(self):
- self.opacity = 0
- super(CarouselDialog, self).open()
- anim = Animation(opacity=1, d=.5).start(self)
-
- def dismiss(self):
- def on_complete(*l):
- super(CarouselDialog, self).dismiss()
- anim = Animation(opacity=0, d=.5)
- anim.bind(on_complete=on_complete)
- anim.start(self)
-
- def add_widget(self, widget, index=0):
- if isinstance(widget, Carousel):
- super(CarouselDialog, self).add_widget(widget, index)
- return
- if 'carousel_content' not in self.ids.keys():
- super(CarouselDialog, self).add_widget(widget)
- return
- self.carousel_content.add_widget(widget, index)
-
-
-
-class NFCTransactionDialog(AnimatedPopup):
-
- mode = OptionProperty('send', options=('send','receive'))
-
- scanner = ObjectProperty(None)
-
- def __init__(self, **kwargs):
- # Delayed Init
- global NFCSCanner
- if NFCSCanner is None:
- from electrum_gui.kivy.nfc_scanner import NFCScanner
- self.scanner = NFCSCanner
-
- super(NFCTransactionDialog, self).__init__(**kwargs)
- self.scanner.nfc_init()
- self.scanner.bind()
-
- def on_parent(self, instance, value):
- sctr = self.ids.sctr
- if value:
- def _cmp(*l):
- anim = Animation(rotation=2, scale=1, opacity=1)
- anim.start(sctr)
- anim.bind(on_complete=_start)
-
- def _start(*l):
- anim = Animation(rotation=350, scale=2, opacity=0)
- anim.start(sctr)
- anim.bind(on_complete=_cmp)
- _start()
- return
- Animation.cancel_all(sctr)
-
-
-class InfoBubble(Bubble):
- '''Bubble to be used to display short Help Information'''
-
- message = StringProperty(_('Nothing set !'))
- '''Message to be displayed; defaults to "nothing set"'''
-
- icon = StringProperty('')
- ''' Icon to be displayed along with the message defaults to ''
-
- :attr:`icon` is a `StringProperty` defaults to `''`
- '''
-
- fs = BooleanProperty(False)
- ''' Show Bubble in half screen mode
-
- :attr:`fs` is a `BooleanProperty` defaults to `False`
- '''
-
- modal = BooleanProperty(False)
- ''' Allow bubble to be hidden on touch.
-
- :attr:`modal` is a `BooleanProperty` defauult to `False`.
- '''
-
- exit = BooleanProperty(False)
- '''Indicates whether to exit app after bubble is closed.
-
- :attr:`exit` is a `BooleanProperty` defaults to False.
- '''
-
- dim_background = BooleanProperty(False)
- ''' Indicates Whether to draw a background on the windows behind the bubble.
-
- :attr:`dim` is a `BooleanProperty` defaults to `False`.
- '''
-
- def on_touch_down(self, touch):
- if self.modal:
- return True
- self.hide()
- if self.collide_point(*touch.pos):
- return True
-
- def show(self, pos, duration, width=None, modal=False, exit=False):
- '''Animate the bubble into position'''
- self.modal, self.exit = modal, exit
- if width:
- self.width = width
- if self.modal:
- from kivy.uix.modalview import ModalView
- self._modal_view = m = ModalView()
- Window.add_widget(m)
- m.add_widget(self)
- else:
- Window.add_widget(self)
- # wait for the bubble to adjust it's size according to text then animate
- Clock.schedule_once(lambda dt: self._show(pos, duration))
-
- def _show(self, pos, duration):
-
- def on_stop(*l):
- if duration:
- Clock.schedule_once(self.hide, duration + .5)
-
- self.opacity = 0
- arrow_pos = self.arrow_pos
- if arrow_pos[0] in ('l', 'r'):
- pos = pos[0], pos[1] - (self.height/2)
- else:
- pos = pos[0] - (self.width/2), pos[1]
-
- self.limit_to = Window
-
- anim = Animation(opacity=1, pos=pos, d=.32)
- anim.bind(on_complete=on_stop)
- anim.cancel_all(self)
- anim.start(self)
-
-
- def hide(self, now=False):
- ''' Auto fade out the Bubble
- '''
- def on_stop(*l):
- if self.modal:
- m = self._modal_view
- m.remove_widget(self)
- Window.remove_widget(m)
- Window.remove_widget(self)
- if self.exit:
- App.get_running_app().stop()
- import sys
- sys.exit()
- if now:
- return on_stop()
-
- anim = Animation(opacity=0, d=.25)
- anim.bind(on_complete=on_stop)
- anim.cancel_all(self)
- anim.start(self)
-
-
-class InfoContent(Widget):
- '''Abstract class to be used to add to content to InfoDialog'''
- pass
-
-
-class InfoButton(Button):
- '''Button that is auto added to the dialog when setting `buttons:`
- property.
- '''
- pass
-
-
-class EventsDialog(AnimatedPopup):
- ''' Abstract Popup that provides the following events
- .. events::
- `on_release`
- `on_press`
- '''
-
- __events__ = ('on_release', 'on_press')
-
- def __init__(self, **kwargs):
- super(EventsDialog, self).__init__(**kwargs)
- self._on_release = kwargs.get('on_release')
- Window.bind(size=self.on_size,
- rotation=self.on_size)
- self.on_size(Window, Window.size)
-
- def on_size(self, instance, value):
- if app.ui_mode[0] == 'p':
- self.size = Window.size
- else:
- #tablet
- if app.orientation[0] == 'p':
- #portrait
- self.size = Window.size[0]/1.67, Window.size[1]/1.4
- else:
- self.size = Window.size[0]/2.5, Window.size[1]
-
- def on_release(self, instance):
- pass
-
- def on_press(self, instance):
- pass
-
- def close(self):
- self._on_release = None
- self.dismiss()
-
-
-class InfoDialog(EventsDialog):
- ''' A dialog box meant to display info along with buttons at the bottom
-
- '''
-
- buttons = ListProperty([_('ok'), _('cancel')])
- '''List of Buttons to be displayed at the bottom'''
-
- def __init__(self, **kwargs):
- self._old_buttons = self.buttons
- super(InfoDialog, self).__init__(**kwargs)
- self.on_buttons(self, self.buttons)
-
- def on_buttons(self, instance, value):
- if 'buttons_layout' not in self.ids.keys():
- return
- if value == self._old_buttons:
- return
- blayout = self.ids.buttons_layout
- blayout.clear_widgets()
- for btn in value:
- ib = InfoButton(text=btn)
- ib.bind(on_press=partial(self.dispatch, 'on_press'))
- ib.bind(on_release=partial(self.dispatch, 'on_release'))
- blayout.add_widget(ib)
- self._old_buttons = value
- pass
-
- def add_widget(self, widget, index=0):
- if isinstance(widget, InfoContent):
- self.ids.info_content.add_widget(widget, index=index)
- else:
- super(InfoDialog, self).add_widget(widget)
-
-
-class TakeInputDialog(InfoDialog):
- ''' A simple Dialog for displaying a message and taking a input
- using a Textinput
- '''
-
- text = StringProperty('Nothing set yet')
-
- readonly = BooleanProperty(False)
-
-
-class EditLabelDialog(TakeInputDialog):
- pass
-
-
-
-class ImportPrivateKeysDialog(TakeInputDialog):
- pass
-
-
-
-class ShowMasterPublicKeyDialog(TakeInputDialog):
- pass
-
-
-class EditDescriptionDialog(TakeInputDialog):
-
- pass
-
-
-class PrivateKeyDialog(InfoDialog):
-
- private_key = StringProperty('')
- ''' private key to be displayed in the TextInput
- '''
-
- address = StringProperty('')
- ''' address to be displayed in the dialog
- '''
-
-
-class SignVerifyDialog(InfoDialog):
-
- address = StringProperty('')
- '''current address being verified'''
-
-
-
-class MessageBox(InfoDialog):
-
- image = StringProperty('atlas://gui/kivy/theming/light/info')
- '''path to image to be displayed on the left'''
-
- message = StringProperty('Empty Message')
- '''Message to be displayed on the dialog'''
-
- def __init__(self, **kwargs):
- super(MessageBox, self).__init__(**kwargs)
- self.title = kwargs.get('title', _('Message'))
-
-
-class MessageBoxExit(MessageBox):
-
- def __init__(self, **kwargs):
- super(MessageBox, self).__init__(**kwargs)
- self.title = kwargs.get('title', _('Exiting'))
-
-class MessageBoxError(MessageBox):
-
- def __init__(self, **kwargs):
- super(MessageBox, self).__init__(**kwargs)
- self.title = kwargs.get('title', _('Error'))
-
-
-class WalletAddressesDialog(CarouselDialog):
-
- def __init__(self, **kwargs):
- super(WalletAddressesDialog, self).__init__(**kwargs)
- CarouselHeader = Factory.CarouselHeader
- ch = CarouselHeader()
- ch.slide = 0 # idx
-
- # delayed init
- global ScreenAddress
- if not ScreenAddress:
- from electrum_gui.kivy.screens import ScreenAddress
- slide = ScreenAddress()
-
- slide.tab=ch
-
- labels = app.wallet.labels
- addresses = app.wallet.addresses()
- _labels = {}
- for address in addresses:
- _labels[labels.get(address, address)] = address
-
- slide.labels = _labels
-
- self.add_widget(slide)
- self.add_widget(ch)
- Clock.schedule_once(lambda dt: self.delayed_init(slide))
-
- def delayed_init(self, slide):
- # add a tab for each wallet
- # for wallet in wallets
- slide.ids.btn_address.values = values = slide.labels.keys()
- slide.ids.btn_address.text = values[0]
-
-
-
-class RecentActivityDialog(CarouselDialog):
-
- def send_payment(self, address):
- tabs = app.root.main_screen.ids.tabs
- screen_send = tabs.ids.screen_send
- # remove self
- self.dismiss()
- # switch_to the send screen
- tabs.ids.panel.switch_to(tabs.ids.tab_send)
- # populate
- screen_send.ids.payto_e.text = address
-
- def populate_inputs_outputs(self, app, tx_hash):
- if tx_hash:
- tx = app.wallet.transactions.get(tx_hash)
- self.ids.list_outputs.content_adapter.data = \
- [(address, app.gui.main_gui.format_amount(value))\
- for address, value in tx.outputs]
- self.ids.list_inputs.content_adapter.data = \
- [(input['address'], input['prevout_hash'])\
- for input in tx.inputs]
-
-
-class CreateAccountDialog(EventsDialog):
- ''' Abstract dialog to be used as the base for all Create Account Dialogs
- '''
- crcontent = ObjectProperty(None)
-
- def add_widget(self, widget, index=0):
- if not self.crcontent:
- super(CreateAccountDialog, self).add_widget(widget)
- else:
- self.crcontent.add_widget(widget, index=index)
-
-
-class CreateRestoreDialog(CreateAccountDialog):
- ''' Initial Dialog for creating or restoring seed'''
-
- def on_parent(self, instance, value):
- if value:
- self.ids.but_close.disabled = True
- self.ids.but_close.opacity = 0
- self._back = _back = partial(app.dispatch, 'on_back')
- app.navigation_higherarchy.append(_back)
-
- def close(self):
- if self._back in app.navigation_higherarchy:
- app.navigation_higherarchy.pop()
- self._back = None
- super(CreateRestoreDialog, self).close()
-
-
-class InitSeedDialog(CreateAccountDialog):
-
- seed_msg = StringProperty('')
- '''Text to be displayed in the TextInput'''
-
- message = StringProperty('')
- '''Message to be displayed under seed'''
-
- seed = ObjectProperty(None)
-
- def on_parent(self, instance, value):
- if value:
- stepper = self.ids.stepper
- stepper.opacity = 1
- stepper.source = 'atlas://gui/kivy/theming/light/stepper_full'
- self._back = _back = partial(self.ids.back.dispatch, 'on_release')
- app.navigation_higherarchy.append(_back)
-
- def close(self):
- if self._back in app.navigation_higherarchy:
- app.navigation_higherarchy.pop()
- self._back = None
- super(InitSeedDialog, self).close()
-
-class VerifySeedDialog(CreateAccountDialog):
-
- pass
-
-class RestoreSeedDialog(CreateAccountDialog):
-
- def on_parent(self, instance, value):
- if value:
- tis = self.ids.text_input_seed
- tis.focus = True
- tis._keyboard.bind(on_key_down=self.on_key_down)
- stepper = self.ids.stepper
- stepper.opacity = 1
- stepper.source = ('atlas://gui/kivy/theming'
- '/light/stepper_restore_seed')
- self._back = _back = partial(self.ids.back.dispatch, 'on_release')
- app.navigation_higherarchy.append(_back)
-
- def on_key_down(self, keyboard, keycode, key, modifiers):
- if keycode[0] in (13, 271):
- self.on_enter()
- return True
- #super
-
- def on_enter(self):
- #self._remove_keyboard()
- # press next
- self.ids.next.dispatch('on_release')
-
- def _remove_keyboard(self):
- tis = self.ids.text_input_seed
- if tis._keyboard:
- tis._keyboard.unbind(on_key_down=self.on_key_down)
- tis.focus = False
-
- def close(self):
- self._remove_keyboard()
- if self._back in app.navigation_higherarchy:
- app.navigation_higherarchy.pop()
- self._back = None
- super(RestoreSeedDialog, self).close()
-
-class NewContactDialog(Popup):
-
- qrscr = ObjectProperty(None)
- _decoder = None
-
- def load_qr_scanner(self):
- global QRScanner
- if not QRScanner:
- from electrum_gui.kivy.qr_scanner import QRScanner
- qrscr = self.qrscr
- if not qrscr:
- self.qrscr = qrscr = QRScanner(opacity=0)
- #pos=self.pos, size=self.size)
- #self.bind(pos=qrscr.setter('pos'),
- # size=qrscr.setter('size')
- qrscr.bind(symbols=self.on_symbols)
- bl = self.ids.bl
- bl.clear_widgets()
- bl.add_widget(qrscr)
- qrscr.opacity = 1
- Animation(height=dp(280)).start(self)
- Animation(opacity=1).start(self)
- qrscr.start()
-
- def on_symbols(self, instance, value):
- instance.stop()
- self.remove_widget(instance)
- self.ids.but_contact.dispatch('on_release')
- global decode_uri
- if not decode_uri:
- from electrum_gui.kivy.qr_scanner import decode_uri
- uri = decode_uri(value[0].data)
- self.ids.ti.text = uri.get('address', 'empty')
- self.ids.ti_lbl.text = uri.get('label', 'empty')
- self.ids.ti_lbl.focus = True
-
-
-class PasswordRequiredDialog(InfoDialog):
-
- pass
-
-
-class ChangePasswordDialog(CreateAccountDialog):
-
- message = StringProperty(_('Empty Message'))
- '''Message to be displayed.'''
-
- mode = OptionProperty('new',
- options=('new', 'confirm', 'create', 'restore'))
- ''' Defines the mode of the password dialog.'''
-
- def validate_new_password(self):
- self.ids.next.dispatch('on_release')
-
- def on_parent(self, instance, value):
- if value:
- stepper = self.ids.stepper
- stepper.opacity = 1
- t_wallet_name = self.ids.ti_wallet_name
- if self.mode in ('create', 'restore'):
- t_wallet_name.text = 'Default Wallet'
- t_wallet_name.readonly = True
- self.ids.ti_new_password.focus = True
- else:
- t_wallet_name.text = ''
- t_wallet_name.readonly = False
- t_wallet_name.focus = True
- stepper.source = 'atlas://gui/kivy/theming/light/stepper_left'
- self._back = _back = partial(self.ids.back.dispatch, 'on_release')
- app.navigation_higherarchy.append(_back)
-
- def close(self):
- ids = self.ids
- ids.ti_wallet_name.text = ""
- ids.ti_wallet_name.focus = False
- ids.ti_password.text = ""
- ids.ti_password.focus = False
- ids.ti_new_password.text = ""
- ids.ti_new_password.focus = False
- ids.ti_confirm_password.text = ""
- ids.ti_confirm_password.focus = False
- if self._back in app.navigation_higherarchy:
- app.navigation_higherarchy.pop()
- self._back = None
- super(ChangePasswordDialog, self).close()
-
-
-
-class Dialog(Popup):
-
- content_padding = NumericProperty('2dp')
- '''Padding for the content area of the dialog defaults to 2dp
- '''
-
- buttons_padding = NumericProperty('2dp')
- '''Padding for the bottns area of the dialog defaults to 2dp
- '''
-
- buttons_height = NumericProperty('40dp')
- '''Height to be used for the Buttons at the bottom
- '''
-
- def close(self):
- self.dismiss()
-
- def add_content(self, widget, index=0):
- self.ids.layout_content.add_widget(widget, index)
-
- def add_button(self, widget, index=0):
- self.ids.layout_buttons.add_widget(widget, index)
-
-
-class SaveDialog(Popup):
-
- filename = StringProperty('')
- '''The default file name provided
- '''
-
- filters = ListProperty([])
- ''' list of files to be filtered and displayed defaults to allow all
- '''
-
- path = StringProperty(DEFAULT_PATH)
- '''path to be loaded by default in this dialog
- '''
-
- file_chooser = ObjectProperty(None)
- '''link to the file chooser object inside the dialog
- '''
-
- text_input = ObjectProperty(None)
- '''
- '''
-
- cancel_button = ObjectProperty(None)
- '''
- '''
-
- save_button = ObjectProperty(None)
- '''
- '''
-
- def close(self):
- self.dismiss()
-
-
-class LoadDialog(SaveDialog):
-
- def _get_load_btn(self):
- return self.save_button
-
- load_button = AliasProperty(_get_load_btn, None, bind=('save_button', ))
- '''Alias to the Save Button to be used as LoadButton
- '''
-
- def __init__(self, **kwargs):
- super(LoadDialog, self).__init__(**kwargs)
- self.load_button.text=_("Load")
diff --git a/gui/kivy/drawer.py b/gui/kivy/drawer.py
@@ -1,187 +0,0 @@
-
-from kivy.uix.stencilview import StencilView
-from kivy.uix.boxlayout import BoxLayout
-from kivy.uix.image import Image
-
-from kivy.animation import Animation
-from kivy.clock import Clock
-from kivy.properties import OptionProperty, NumericProperty, ObjectProperty
-
-# delayed import
-app = None
-
-
-class Drawer(StencilView):
-
- state = OptionProperty('closed',
- options=('closed', 'open', 'opening', 'closing'))
- '''This indicates the current state the drawer is in.
-
- :attr:`state` is a `OptionProperty` defaults to `closed`. Can be one of
- `closed`, `open`, `opening`, `closing`.
- '''
-
- scroll_timeout = NumericProperty(200)
- '''Timeout allowed to trigger the :data:`scroll_distance`,
- in milliseconds. If the user has not moved :data:`scroll_distance`
- within the timeout, the scrolling will be disabled and the touch event
- will go to the children.
-
- :data:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty`
- and defaults to 200 (milliseconds)
- '''
-
- scroll_distance = NumericProperty('9dp')
- '''Distance to move before scrolling the :class:`Drawer` in pixels.
- As soon as the distance has been traveled, the :class:`Drawer` will
- start to scroll, and no touch event will go to children.
- It is advisable that you base this value on the dpi of your target
- device's screen.
-
- :data:`scroll_distance` is a :class:`~kivy.properties.NumericProperty`
- and defaults to 20dp.
- '''
-
- drag_area = NumericProperty(.1)
- '''The percentage of area on the left edge that triggers the opening of
- the drawer. from 0-1
-
- :attr:`drag_area` is a `NumericProperty` defaults to 2
- '''
-
- _hidden_widget = ObjectProperty(None)
- _overlay_widget = ObjectProperty(None)
-
- def __init__(self, **kwargs):
- super(Drawer, self).__init__(**kwargs)
- self.bind(pos=self._do_layout,
- size=self._do_layout,
- children=self._do_layout)
-
- def _do_layout(self, instance, value):
- if not self._hidden_widget or not self._overlay_widget:
- return
- self._overlay_widget.height = self._hidden_widget.height =\
- self.height
-
- def on_touch_down(self, touch):
- if self.disabled:
- return
-
- if not self.collide_point(*touch.pos):
- return
-
- touch.grab(self)
-
- global app
- if not app:
- from kivy.app import App
- app = App.get_running_app()
-
- # skip on tablet mode
- if app.ui_mode[0] == 't':
- return super(Drawer, self).on_touch_down(touch)
-
- state = self.state
- touch.ud['send_touch_down'] = False
- start = 0 if state[0] == 'c' else self._hidden_widget.right
- drag_area = ((self.width * self.drag_area)
- if self.state[0] == 'c' else
- self.width)
- if touch.x not in range(int(start), int(drag_area)):
- return super(Drawer, self).on_touch_down(touch)
- self._touch = touch
- Clock.schedule_once(self._change_touch_mode,
- self.scroll_timeout/1000.)
- touch.ud['in_drag_area'] = True
- touch.ud['send_touch_down'] = True
- return
-
- def on_touch_move(self, touch):
- if not touch.grab_current:
- return
-
- # skip on tablet mode
- if app.ui_mode[0] == 't':
- return super(Drawer, self).on_touch_move(touch)
-
- if not touch.ud.get('in_drag_area', None):
- return super(Drawer, self).on_touch_move(touch)
-
- ov = self._overlay_widget
- ov.x=min(self._hidden_widget.width,
- max(ov.x + touch.dx*2, 0))
- #_anim = Animation(x=x, duration=1/2, t='in_out_quart')
- #_anim.cancel_all(ov)
- #_anim.start(ov)
-
- if abs(touch.x - touch.ox) < self.scroll_distance:
- return
- touch.ud['send_touch_down'] = False
- Clock.unschedule(self._change_touch_mode)
- self._touch = None
- self.state = 'opening' if touch.dx > 0 else 'closing'
- touch.ox = touch.x
- return
-
- def _change_touch_mode(self, *args):
- if not self._touch:
- return
- touch = self._touch
- touch.ud['in_drag_area'] = False
- touch.ud['send_touch_down'] = False
- self._touch = None
- super(Drawer, self).on_touch_down(touch)
- return
-
- def on_touch_up(self, touch):
- if not touch.grab_current:
- return
-
- # skip on tablet mode
- if app.ui_mode[0] == 't':
- return super(Drawer, self).on_touch_down(touch)
-
- if touch.ud.get('send_touch_down', None):
- Clock.unschedule(self._change_touch_mode)
- Clock.schedule_once(
- lambda dt: super(Drawer, self).on_touch_down(touch), -1)
- if touch.ud.get('in_drag_area', None):
- touch.ud['in_drag_area'] = False
- Animation.cancel_all(self._overlay_widget)
- anim = Animation(x=self._hidden_widget.width
- if self.state[0] == 'o' else 0,
- d=.1, t='linear')
- anim.bind(on_complete = self._complete_drawer_animation)
- anim.start(self._overlay_widget)
- Clock.schedule_once(
- lambda dt: super(Drawer, self).on_touch_up(touch), 0)
-
- def _complete_drawer_animation(self, *args):
- self.state = 'open' if self.state[0] == 'o' else 'closed'
-
- def add_widget(self, widget, index=1):
- if not widget:
- return
- children = self.children
- len_children = len(children)
- if len_children == 2:
- Logger.debug('Drawer: No more than two widgets allowed')
- return
-
- super(Drawer, self).add_widget(widget)
- if len_children == 0:
- # first widget add it to the hidden/drawer section
- self._hidden_widget = widget
- return
- # Second Widget
- self._overlay_widget = widget
-
- def remove_widget(self, widget):
- super(Drawer, self).remove_widget(self)
- if widget == self._hidden_widget:
- self._hidden_widget = None
- return
- if widget == self._overlay_widget:
- self._overlay_widget = None
- return-
\ No newline at end of file
diff --git a/gui/kivy/gridview.py b/gui/kivy/gridview.py
@@ -1,203 +0,0 @@
-from kivy.uix.boxlayout import BoxLayout
-from kivy.adapters.dictadapter import DictAdapter
-from kivy.adapters.listadapter import ListAdapter
-from kivy.properties import ObjectProperty, ListProperty, AliasProperty
-from kivy.uix.listview import (ListItemButton, ListItemLabel, CompositeListItem,
- ListView)
-from kivy.lang import Builder
-from kivy.metrics import dp, sp
-
-Builder.load_string('''
-<GridView>
- header_view: header_view
- content_view: content_view
- BoxLayout:
- orientation: 'vertical'
- padding: '0dp', '2dp'
- BoxLayout:
- id: header_box
- orientation: 'vertical'
- size_hint: 1, None
- height: '30dp'
- ListView:
- id: header_view
- BoxLayout:
- id: content_box
- orientation: 'vertical'
- ListView:
- id: content_view
-
-<-HorizVertGrid>
- header_view: header_view
- content_view: content_view
- ScrollView:
- id: scrl
- do_scroll_y: False
- RelativeLayout:
- size_hint_x: None
- width: max(scrl.width, dp(sum(root.widths)))
- BoxLayout:
- orientation: 'vertical'
- padding: '0dp', '2dp'
- BoxLayout:
- id: header_box
- orientation: 'vertical'
- size_hint: 1, None
- height: '30dp'
- ListView:
- id: header_view
- BoxLayout:
- id: content_box
- orientation: 'vertical'
- ListView:
- id: content_view
-
-''')
-
-class GridView(BoxLayout):
- """Workaround solution for grid view by using 2 list view.
- Sometimes the height of lines is shown properly."""
-
- def _get_hd_adpt(self):
- return self.ids.header_view.adapter
-
- header_adapter = AliasProperty(_get_hd_adpt, None)
- '''
- '''
-
- def _get_cnt_adpt(self):
- return self.ids.content_view.adapter
-
- content_adapter = AliasProperty(_get_cnt_adpt, None)
- '''
- '''
-
- headers = ListProperty([])
- '''
- '''
-
- widths = ListProperty([])
- '''
- '''
-
- data = ListProperty([])
- '''
- '''
-
- getter = ObjectProperty(lambda item, i: item[i])
- '''
- '''
- on_context_menu = ObjectProperty(None)
-
- def __init__(self, **kwargs):
- super(GridView, self).__init__(**kwargs)
- self._from_widths = False
- #self.on_headers(self, self.headers)
-
- def on_widths(self, instance, value):
- self._from_widths = True
- self.on_headers(instance, self.headers)
- self._from_widths = False
-
- def on_headers(self, instance, value):
- if not self._from_widths:
- return
- if not (value and self.canvas and self.headers):
- return
- widths = self.widths
- if len(self.widths) != len(value):
- return
- #if widths is not None:
- # widths = ['%sdp' % i for i in widths]
-
- def generic_args_converter(row_index,
- item,
- is_header=True,
- getter=self.getter):
- cls_dicts = []
- _widths = self.widths
- getter = self.getter
- on_context_menu = self.on_context_menu
-
- for i, header in enumerate(self.headers):
- kwargs = {
- 'padding': ('2dp','2dp'),
- 'halign': 'center',
- 'valign': 'middle',
- 'size_hint_y': None,
- 'shorten': True,
- 'height': '30dp',
- 'text_size': (_widths[i], dp(30)),
- 'text': getter(item, i),
- }
-
- kwargs['font_size'] = '9sp'
- if is_header:
- kwargs['deselected_color'] = kwargs['selected_color'] =\
- [0, 1, 1, 1]
- else: # this is content
- kwargs['deselected_color'] = 1, 1, 1, 1
- if on_context_menu is not None:
- kwargs['on_press'] = on_context_menu
-
- if widths is not None: # set width manually
- kwargs['size_hint_x'] = None
- kwargs['width'] = widths[i]
-
- cls_dicts.append({
- 'cls': ListItemButton,
- 'kwargs': kwargs,
- })
-
- return {
- 'id': item[-1],
- 'size_hint_y': None,
- 'height': '30dp',
- 'cls_dicts': cls_dicts,
- }
-
- def header_args_converter(row_index, item):
- return generic_args_converter(row_index, item)
-
- def content_args_converter(row_index, item):
- return generic_args_converter(row_index, item, is_header=False)
-
-
- self.ids.header_view.adapter = ListAdapter(data=[self.headers],
- args_converter=header_args_converter,
- selection_mode='single',
- allow_empty_selection=False,
- cls=CompositeListItem)
-
- self.ids.content_view.adapter = ListAdapter(data=self.data,
- args_converter=content_args_converter,
- selection_mode='single',
- allow_empty_selection=False,
- cls=CompositeListItem)
- self.content_adapter.bind_triggers_to_view(self.ids.content_view._trigger_reset_populate)
-
-class HorizVertGrid(GridView):
- pass
-
-
-if __name__ == "__main__":
- from kivy.app import App
- class MainApp(App):
-
- def build(self):
- data = []
- for i in range(90):
- data.append((str(i), str(i)))
- self.data = data
- return Builder.load_string('''
-BoxLayout:
- orientation: 'vertical'
- HorizVertGrid:
- on_parent: if args[1]: self.content_adapter.data = app.data
- headers:['Address', 'Previous output']
- widths: [400, 500]
-
-<Label>
- font_size: '16sp'
-''')
- MainApp().run()
diff --git a/gui/kivy/installwizard.py b/gui/kivy/installwizard.py
@@ -1,328 +0,0 @@
-from electrum import Wallet
-from electrum.i18n import _
-
-from kivy.app import App
-from kivy.uix.widget import Widget
-from kivy.core.window import Window
-from kivy.clock import Clock
-
-from electrum_gui.kivy.dialog import CreateRestoreDialog
-#from network_dialog import NetworkDialog
-#from util import *
-#from amountedit import AmountEdit
-
-import sys
-import threading
-from functools import partial
-
-# global Variables
-app = App.get_running_app()
-
-
-class InstallWizard(Widget):
- '''Installation Wizard. Responsible for instantiating the
- creation/restoration of wallets.
-
- events::
- `on_wizard_complete` Fired when the wizard is done creating/ restoring
- wallet/s.
- '''
-
- __events__ = ('on_wizard_complete', )
-
- def __init__(self, config, network, storage):
- super(InstallWizard, self).__init__()
- self.config = config
- self.network = network
- self.storage = storage
-
- def waiting_dialog(self, task,
- msg= _("Electrum is generating your addresses,"
- " please wait."),
- on_complete=None):
- '''Perform a blocking task in the background by running the passed
- method in a thread.
- '''
-
- def target():
-
- # run your threaded function
- try:
- task()
- except Exception as err:
- Clock.schedule_once(lambda dt: app.show_error(str(err)))
-
- # on completion hide message
- Clock.schedule_once(lambda dt: app.info_bubble.hide(now=True), -1)
-
- # call completion routine
- if on_complete:
- Clock.schedule_once(lambda dt: on_complete())
-
- app.show_info_bubble(
- text=msg, icon='atlas://gui/kivy/theming/light/important',
- pos=Window.center, width='200sp', arrow_pos=None, modal=True)
- t = threading.Thread(target = target)
- t.start()
-
- def run(self):
- '''Entry point of our Installation wizard
- '''
- CreateRestoreDialog(on_release=self.on_creatrestore_complete).open()
-
- def on_creatrestore_complete(self, dialog, button):
- if not button:
- return self.dispatch('on_wizard_complete', None)
-
- #gap = self.config.get('gap_limit', 5)
- #if gap !=5:
- # wallet.gap_limit = gap_limit
- # wallet.storage.put('gap_limit', gap, True)
-
- dialog.close()
- if button == dialog.ids.create:
- # create
- wallet = Wallet(self.storage)
- self.change_password_dialog(wallet=wallet)
- elif button == dialog.ids.restore:
- # restore
- wallet = None
- self.restore_seed_dialog(wallet)
- #if button == dialog.ids.watching:
- #TODO: not available in the new design
- # self.action = 'watching'
- else:
- self.dispatch('on_wizard_complete', None)
-
- def restore_seed_dialog(self, wallet):
- from electrum_gui.kivy.dialog import RestoreSeedDialog
- RestoreSeedDialog(
- on_release=partial(self.on_verify_restore_ok, wallet)).open()
-
- def on_verify_restore_ok(self, wallet, _dlg, btn, restore=False):
-
- if _dlg.ids.back == btn:
- _dlg.close()
- CreateRestoreDialog(
- on_release=self.on_creatrestore_complete).open()
- return
-
- seed = unicode(_dlg.ids.text_input_seed.text)
- if not seed:
- app.show_error(_("No seed!"), duration=.5)
- return
-
- try:
- wallet = Wallet.from_seed(seed, self.storage)
- except Exception as err:
- _dlg.close()
- return app.show_error(str(err) + '\n App will now exit',
- exit=True, modal=True, duration=.5)
- _dlg.close()
- return self.change_password_dialog(wallet=wallet, mode='restore')
-
-
- def init_seed_dialog(self, wallet=None, instance=None, password=None,
- wallet_name=None, mode='create'):
- # renamed from show_seed()
- '''Can be called directly (password is None)
- or from a password-protected callback (password is not None)'''
-
- if not wallet or not wallet.seed:
- if instance == None:
- wallet.init_seed(None)
- else:
- return app.show_error(_('No seed'))
-
- if password is None or not instance:
- seed = wallet.get_mnemonic(None)
- else:
- try:
- seed = self.wallet.get_seed(password)
- except Exception:
- return app.show_error(_('Incorrect Password'))
-
- brainwallet = seed
-
- msg2 = _("[color=#414141]"+\
- "[b]PLEASE WRITE DOWN YOUR SEED PASS[/b][/color]"+\
- "[size=9]\n\n[/size]" +\
- "[color=#929292]If you ever forget your pincode, your seed" +\
- " phrase will be the [color=#EB984E]"+\
- "[b]only way to recover[/b][/color] your wallet. Your " +\
- " [color=#EB984E][b]Bitcoins[/b][/color] will otherwise be" +\
- " [color=#EB984E][b]lost forever![/b][/color]")
-
- if wallet.imported_keys:
- msg2 += "[b][color=#ff0000ff]" + _("WARNING") + "[/color]:[/b] " +\
- _("Your wallet contains imported keys. These keys cannot" +\
- " be recovered from seed.")
-
- def on_ok_press(_dlg, _btn):
- _dlg.close()
- if _btn != _dlg.ids.confirm:
- if not instance:
- self.change_password_dialog(wallet)
- return
- # confirm
- if instance is None:
- # in initial phase
- def create(password):
- try:
- password = None if not password else password
- wallet.save_seed(password)
- except Exception as err:
- Logger.Info('Wallet: {}'.format(err))
- Clock.schedule_once(lambda dt:
- app.show_error(err))
- wallet.synchronize() # generate first addresses offline
- self.waiting_dialog(
- partial(create, password),
- on_complete=partial(self.load_network, wallet, mode=mode))
-
- from electrum_gui.kivy.dialog import InitSeedDialog
- InitSeedDialog(message=msg2,
- seed_msg=brainwallet, seed=seed, on_release=on_ok_press).open()
-
- def change_password_dialog(self, wallet=None, instance=None, mode='create'):
- """Can be called directly (instance is None)
- or from a callback (instance is not None)"""
-
- if instance and not wallet.seed:
- return ShowError(_('No seed !!'), exit=True, modal=True)
-
- if instance is not None:
- if wallet.use_encryption:
- msg = (
- _('Your wallet is encrypted. Use this dialog to change" + \
- " your password.') + '\n' + _('To disable wallet" + \
- " encryption, enter an empty new password.'))
- mode = 'confirm'
- else:
- msg = _('Your wallet keys are not encrypted')
- mode = 'new'
- else:
- msg = _("Please choose a password to encrypt your wallet keys.") +\
- '\n' + _("Leave these fields empty if you want to disable" + \
- " encryption.")
-
- def on_release(_dlg, _btn):
- ti_password = _dlg.ids.ti_password
- ti_new_password = _dlg.ids.ti_new_password
- ti_confirm_password = _dlg.ids.ti_confirm_password
- if _btn != _dlg.ids.next:
- if mode == 'restore':
- # back is disabled cause seed is already set
- return
- _dlg.close()
- if not instance:
- # back on create
- CreateRestoreDialog(
- on_release=self.on_creatrestore_complete).open()
- return
-
- # Confirm
- wallet_name = _dlg.ids.ti_wallet_name.text
- password = (unicode(ti_password.text)
- if wallet.use_encryption else
- None)
- new_password = unicode(ti_new_password.text)
- new_password2 = unicode(ti_confirm_password.text)
-
- if new_password != new_password2:
- ti_password.text = ""
- ti_new_password.text = ""
- ti_confirm_password.text = ""
- if ti_password.disabled:
- ti_new_password.focus = True
- else:
- ti_password.focus = True
- return app.show_error(_('Passwords do not match'), duration=.5)
-
- if mode == 'restore':
- def on_complete(*l):
- _dlg.close()
- self.load_network(wallet, mode='restore')
-
- self.waiting_dialog(lambda: wallet.save_seed(new_password),
- msg=_("saving seed"),
- on_complete=on_complete)
- return
- if not instance:
- # create
- _dlg.close()
- #self.load_network(wallet, mode='create')
- return self.init_seed_dialog(password=new_password,
- wallet=wallet, wallet_name=wallet_name, mode=mode)
-
- try:
- seed = wallet.decode_seed(password)
- except BaseException:
- return app.show_error(_('Incorrect Password'), duration=.5)
-
- # test carefully
- try:
- wallet.update_password(seed, password, new_password)
- except BaseException:
- return app.show_error(_('Failed to update password'), exit=True)
- else:
- app.show_info_bubble(
- text=_('Password successfully updated'), duration=1,
- pos=_btn.pos)
- _dlg.close()
-
-
- if instance is None: # in initial phase
- self.load_wallet()
- self.app.update_wallet()
-
- from electrum_gui.kivy.dialog import ChangePasswordDialog
- cpd = ChangePasswordDialog(
- message=msg,
- mode=mode,
- on_release=on_release).open()
-
- def load_network(self, wallet, mode='create'):
- #if not self.config.get('server'):
- if self.network:
- if self.network.interfaces:
- if mode not in ('restore', 'create'):
- self.network_dialog()
- else:
- app.show_error(_('You are offline'))
- self.network.stop()
- self.network = None
-
- if mode in ('restore', 'create'):
- # auto cycle
- self.config.set_key('auto_cycle', True, True)
-
- # start wallet threads
- wallet.start_threads(self.network)
-
- if not mode == 'restore':
- return self.dispatch('on_wizard_complete', wallet)
-
- def get_text(text):
- def set_text(*l): app.info_bubble.ids.lbl.text=text
- Clock.schedule_once(set_text)
-
- def on_complete(*l):
- if not self.network:
- app.show_info(
- _("This wallet was restored offline. It may contain more"
- " addresses than displayed."), duration=.5)
- return self.dispatch('on_wizard_complete', wallet)
-
- if wallet.is_found():
- app.show_info(_("Recovery successful"), duration=.5)
- else:
- app.show_info(_("No transactions found for this seed"),
- duration=.5)
- return self.dispatch('on_wizard_complete', wallet)
-
- self.waiting_dialog(lambda: wallet.restore(get_text),
- on_complete=on_complete)
-
- def on_wizard_complete(self, wallet):
- pass
diff --git a/gui/kivy/main.kv b/gui/kivy/main.kv
@@ -1,7 +1,4 @@
#:import Window kivy.core.window.Window
-#:import _ electrum.i18n._
-#:import partial functools.partial
-
# Custom Global Widgets
@@ -22,37 +19,36 @@
if root.state == 'normal' else 'icon_border')
size: root.size
pos: root.pos
-###########################
-## Gloabal Defaults
-###########################
-
-<Label>
- markup: True
- font_name: 'Roboto'
- font_size: '16sp'
-<ListItemButton>
- font_size: '12sp'
-
-#########################
-# Dialogs
-#########################
-
-################################################
-## Create Dialogs
-################################################
+<Butt_star@ActionToggleButton>:
+ important: True
+ size_hint_x: None
+ width: '32dp'
+ mipmap: True
+ state: 'down' if app.expert_mode else 'normal'
+ background_down: self.background_normal
+ foreground_color: (.466, .466, .466, 1)
+ color_active: (0.235, .588, .89, 1)
+ on_release: app.expert_mode = True if self.state == 'down' else False
+ Image:
+ source: 'atlas://gui/kivy/theming/light/star_big_inactive'
+ center: root.center
+ size: root.width/1.5, self.width
+ color:
+ root.foreground_color if root.state == 'normal' else root.color_active
+ canvas.after:
+ Color:
+ rgba: 1, 1, 1, 1
+ source:
+ allow_stretch: True
+
+<ELTextInput>
+ padding: '10dp', '4dp'
+ background_color: (0.238, 0.589, .996, 1) if self.focus else self.foreground_color
+ foreground_color: 0.531, 0.531, 0.531, 1
+ background_active: 'atlas://gui/kivy/theming/light/textinput_active'
+ background_normal: 'atlas://gui/kivy/theming/light/textinput_active'
-<CreateAccountTextInput@TextInput>
- border: 4, 4, 4, 4
- font_size: '15sp'
- padding: '15dp', '15dp'
- background_color: (1, 1, 1, 1) if self.focus else (0.454, 0.698, 0.909, 1)
- foreground_color: (0.31, 0.31, 0.31, 1) if self.focus else (0.835, 0.909, 0.972, 1)
- hint_text_color: self.foreground_color
- background_active: 'atlas://gui/kivy/theming/light/create_act_text_active'
- background_normal: 'atlas://gui/kivy/theming/light/create_act_text_active'
- size_hint_y: None
- height: '48sp'
<CreateAccountButtonBlue@Button>
canvas.after:
@@ -75,26 +71,40 @@
text_size: self.size
halign: 'center'
valign: 'middle'
+ root: None
background_normal: 'atlas://gui/kivy/theming/light/btn_create_account'
background_down: 'atlas://gui/kivy/theming/light/btn_create_account'
background_disabled_normal: 'atlas://gui/kivy/theming/light/btn_create_act_disabled'
- on_release: self.root.dispatch('on_press', self)
- on_release: self.root.dispatch('on_release', self)
+ on_press: if self.root: self.root.dispatch('on_press', self)
+ on_release: if self.root: self.root.dispatch('on_release', self)
+
<CreateAccountButtonGreen@CreateAccountButtonBlue>
background_color: (1, 1, 1, 1) if self.disabled else (.415, .717, 0, 1 if self.state == 'normal' else .75)
+###########################
+## Gloabal Defaults
+###########################
+<TextInput>
+ on_focus: app._focused_widget = root
+
+<Label>
+ markup: True
+ font_name: 'Roboto'
+ font_size: '16sp'
+
+<ListItemButton>
+ font_size: '12sp'
+
+#########################
+# Dialogs
+#########################
<InfoBubble>
- canvas.before:
- Color:
- rgba: 0, 0, 0, .7 if root.dim_background else 0
- Rectangle:
- size: Window.size
size_hint: None, None
width: '270dp' if root.fs else min(self.width, dp(270))
height: self.width if self.fs else (lbl.texture_size[1] + dp(27))
BoxLayout:
- padding: '5dp'
+ padding: '5dp' if root.fs else 0
Widget:
size_hint: None, 1
width: '4dp' if root.fs else '2dp'
@@ -117,346 +127,11 @@
size_hint: 1, 1
width: 0 if root.fs else (root.width - img.width)
-<-CreateAccountDialog>
- text_color: .854, .925, .984, 1
- auto_dismiss: False
- size_hint: None, None
- canvas.before:
- Color:
- rgba: 0, 0, 0, .9
- Rectangle:
- size: Window.size
- Color:
- rgba: .239, .588, .882, 1
- Rectangle:
- size: Window.size
-
- crcontent: crcontent
- # add electrum icon
- FloatLayout:
- size_hint: None, None
- size: 0, 0
- IconButton:
- id: but_close
- size_hint: None, None
- size: '27dp', '27dp'
- top: Window.height - dp(10)
- right: Window.width - dp(10)
- source: 'atlas://gui/kivy/theming/light/closebutton'
- on_release: root.dispatch('on_press', self)
- on_release: root.dispatch('on_release', self)
- BoxLayout:
- orientation: 'vertical' if self.width < self.height else 'horizontal'
- padding:
- min(dp(42), self.width/8), min(dp(60), self.height/9.7),\
- min(dp(42), self.width/8), min(dp(72), self.height/8)
- spacing: '27dp'
- GridLayout:
- id: grid_logo
- cols: 1
- pos_hint: {'center_y': .5}
- size_hint: 1, .62
- #height: self.minimum_height
- Image:
- id: logo_img
- mipmap: True
- allow_stretch: True
- size_hint: 1, None
- height: '110dp'
- source: 'atlas://gui/kivy/theming/light/electrum_icon640'
- Widget:
- size_hint: 1, None
- height: 0 if stepper.opacity else dp(15)
- Label:
- color: root.text_color
- opacity: 0 if stepper.opacity else 1
- text: 'ELECTRUM'
- size_hint: 1, None
- height: self.texture_size[1] if self.opacity else 0
- font_size: '33sp'
- font_name: 'data/fonts/tron/Tr2n.ttf'
- Image:
- id: stepper
- allow_stretch: True
- opacity: 0
- source: 'atlas://gui/kivy/theming/light/stepper_left'
- size_hint: 1, None
- height: grid_logo.height/2.5 if self.opacity else 0
- Widget:
- size_hint: None, None
- size: '5dp', '5dp'
- GridLayout:
- cols: 1
- id: crcontent
- spacing: '13dp'
-
-<CreateRestoreDialog>
- Label:
- color: root.text_color
- size_hint: 1, None
- text_size: self.width, None
- height: self.texture_size[1]
- text:
- _("Wallet file not found!!")+\
- "\n\n" + _("Do you want to create a new wallet ")+\
- _("or restore an existing one?")
- Widget
- size_hint: 1, None
- height: dp(15)
- GridLayout:
- id: grid
- orientation: 'vertical'
- cols: 1
- spacing: '14dp'
- size_hint: 1, None
- height: self.minimum_height
- CreateAccountButtonGreen:
- id: create
- text: _('Create a Wallet')
- root: root
- CreateAccountButtonBlue:
- id: restore
- text: _('I already have a wallet')
- root: root
- #CreateAccountButtonBlue:
- # id: watching
- # text: _('Create a Watching only wallet')
- # root: root
-
-<RestoreSeedDialog>
- GridLayout
- # leave room for future selection of gap through a widget
- # removed for mobile
- id: text_input_gap
- text: '5'
-
- cols: 1
- padding: 0, '12dp'
- orientation: 'vertical'
- spacing: '12dp'
- size_hint: 1, None
- height: self.minimum_height
- CreateAccountTextInput:
- id: text_input_seed
- size_hint: 1, None
- height: '110dp'
- hint_text:
- _('Enter your seedphrase')
- Label:
- font_size: '12sp'
- text_size: self.width, None
- size_hint: 1, None
- height: self.texture_size[1]
- halign: 'justify'
- valign: 'middle'
- text:
- _('If you need additional information, please check '
- '[color=#0000ff][ref=1]'
- 'https://electrum.org/faq.html#seed[/ref][/color]')
- on_ref_press:
- import webbrowser
- webbrowser.open('https://electrum.org/faq.html#seed')
- GridLayout:
- rows: 1
- spacing: '12dp'
- size_hint: 1, None
- height: self.minimum_height
- CreateAccountButtonBlue:
- id: back
- text: _('Back')
- root: root
- CreateAccountButtonGreen:
- id: next
- text: _('Next')
- root: root
-
-<InitSeedDialog>
- spacing: '12dp'
- GridLayout:
- id: grid
- cols: 1
- pos_hint: {'center_y': .5}
- size_hint_y: None
- height: dp(180)
- orientation: 'vertical'
- Button:
- border: 4, 4, 4, 4
- halign: 'justify'
- valign: 'middle'
- font_size: self.width/21
- text_size: self.width - dp(24), self.height - dp(12)
- #size_hint: 1, None
- #height: self.texture_size[1] + dp(24)
- background_normal: 'atlas://gui/kivy/theming/light/white_bg_round_top'
- background_down: self.background_normal
- text: root.message
- GridLayout:
- rows: 1
- size_hint: 1, .7
- #size_hint_y: None
- #height: but_seed.texture_size[1] + dp(24)
- Button:
- id: but_seed
- border: 4, 4, 4, 4
- halign: 'justify'
- valign: 'middle'
- font_size: self.width/15
- text: root.seed_msg
- text_size: self.width - dp(24), self.height - dp(12)
- background_normal: 'atlas://gui/kivy/theming/light/lightblue_bg_round_lb'
- background_down: self.background_normal
- Button:
- id: bt
- size_hint_x: .25
- background_normal: 'atlas://gui/kivy/theming/light/blue_bg_round_rb'
- background_down: self.background_normal
- Image:
- mipmap: True
- source: 'atlas://gui/kivy/theming/light/qrcode'
- size: bt.size
- center: bt.center
- #on_release:
- GridLayout:
- rows: 1
- spacing: '12dp'
- size_hint: 1, None
- height: self.minimum_height
- CreateAccountButtonBlue:
- id: back
- text: _('Back')
- root: root
- CreateAccountButtonGreen:
- id: confirm
- text: _('Confirm')
- root: root
-
-<ChangePasswordDialog>
- padding: '7dp'
- GridLayout:
- size_hint_y: None
- height: self.minimum_height
- cols: 1
- CreateAccountTextInput:
- id: ti_wallet_name
- hint_text: 'Your Wallet Name'
- multiline: False
- on_text_validate:
- next = ti_new_password if ti_password.disabled else ti_password
- next.focus = True
- Widget:
- size_hint_y: None
- height: '13dp'
- CreateAccountTextInput:
- id: ti_password
- hint_text: 'Enter old pincode'
- size_hint_y: None
- height: 0 if self.disabled else '38sp'
- password: True
- disabled: True if root.mode in ('new', 'create', 'restore') else False
- opacity: 0 if self.disabled else 1
- multiline: False
- on_text_validate:
- #root.validate_old_password()
- ti_new_password.focus = True
- Widget:
- size_hint_y: None
- height: 0 if ti_password.disabled else '13dp'
- CreateAccountTextInput:
- id: ti_new_password
- hint_text: 'Enter new pincode'
- multiline: False
- password: True
- on_text_validate: ti_confirm_password.focus = True
- Widget:
- size_hint_y: None
- height: '13dp'
- CreateAccountTextInput:
- id: ti_confirm_password
- hint_text: 'Confirm pincode'
- password: True
- multiline: False
- on_text_validate: root.validate_new_password()
- Widget
- GridLayout:
- rows: 1
- spacing: '12dp'
- size_hint: 1, None
- height: self.minimum_height
- CreateAccountButtonBlue:
- id: back
- text: _('Back')
- root: root
- disabled: True if root.mode[0] == 'r' else self.disabled
- CreateAccountButtonGreen:
- id: next
- text: _('Confirm') if root.mode[0] == 'r' else _('Next')
- root: root
-
-###############################################
-## Wallet Management
-###############################################
-
-<WalletManagement@ScrollView>
+StencilView:
+ manager: None
canvas.before:
Color:
- rgba: .145, .145, .145, 1
- Rectangle:
- size: root.size
- pos: root.pos
- VGridLayout:
- Wallets:
- id: wallets_section
- Plugins:
- id: plugins_section
- Commands:
- id: commands_section
-
-<WalletManagementItem@BoxLayout>
-
-<Header@WalletManagementItem>
-
-<Wallets@VGridLayout>
- Header
-
-<Plugins@VGridLayout>
- Header
-
-<Commands@VGridLayout>
- Header
-
-################################################
-## This is our Root Widget of the app
-################################################
-StencilView
- manager: manager
- Drawer
- id: drawer
- size: root.size
- WalletManagement
- id: wallet_management
- canvas.before:
- Color:
- rgba: .176, .176, .176, 1
- Rectangle:
- size: self.size
- pos: self.pos
- width:
- (root.width * .877) if app.ui_mode[0] == 'p'\
- else root.width * .35 if app.orientation[0] == 'l'\
- else root.width * .10
- height: root.height
- BoxLayout:
- x: wallet_management.width if app.ui_mode[0] == 't' else 0
- width: (root.width - self.x) if app.ui_mode[0] == 't' else root.width
- size_hint: None, None
- height: root.height
- canvas.before:
- Color
- rgba: 1, 1, 1, 1
- BorderImage
- border: 0, 32, 0, 0
- source: 'atlas://gui/kivy/theming/light/shadow_right'
- pos: root.pos
- size: self.x, self.height
- ScreenManager:
- id: manager-
\ No newline at end of file
+ rgba: 1, 1, 1, 1
+ Rectangle
+ size: self.size
+ pos: self.pos+
\ No newline at end of file
diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py
@@ -1,35 +1,88 @@
import sys
-from decimal import Decimal
+import datetime
from electrum import WalletStorage, Wallet
from electrum.i18n import _, set_language
-from electrum.wallet import format_satoshis
from kivy.app import App
from kivy.core.window import Window
-from kivy.lang import Builder
from kivy.logger import Logger
-from kivy.metrics import inch
from kivy.utils import platform
from kivy.properties import (OptionProperty, AliasProperty, ObjectProperty,
- StringProperty, ListProperty)
+ StringProperty, ListProperty, BooleanProperty)
+from kivy.cache import Cache
from kivy.clock import Clock
+from kivy.factory import Factory
-#inclusions for factory so that widgets can be used in kv
-from electrum_gui.kivy.drawer import Drawer
-from electrum_gui.kivy.dialog import InfoBubble
+from electrum_gui.kivy.uix.drawer import Drawer
-# delayed imports
-notification = None
+# lazy imports for factory so that widgets can be used in kv
+Factory.register('InstallWizard',
+ module='electrum_gui.kivy.uix.dialogs.installwizard')
+Factory.register('InfoBubble', module='electrum_gui.kivy.uix.dialogs')
+Factory.register('ELTextInput', module='electrum_gui.kivy.uix.screens')
+
+# delayed imports: for startup speed on android
+notification = app = Decimal = ref = format_satoshis = is_valid = Builder = None
+inch = None
+util = False
+re = None
+
+# register widget cache for keeping memory down timeout to 4 minutes to cache
+# the data
+Cache.register('electrum_widgets', timeout=240)
class ElectrumWindow(App):
- title = _('Electrum App')
+ def _get_bu(self):
+ assert self.decimal_point in (5,8)
+ return "BTC" if self.decimal_point == 8 else "mBTC"
- wallet = ObjectProperty(None)
- '''Holds the electrum wallet
+ def _set_bu(self, value):
+ try:
+ self.electrum_config.set_key('base_unit', value, True)
+ except AttributeError:
+ Logger.error('Electrum: Config not set '
+ 'While trying to save value to config')
- :attr:`wallet` is a `ObjectProperty` defaults to None.
+ base_unit = AliasProperty(_get_bu, _set_bu, bind=('decimal_point',))
+ '''BTC or UBTC or mBTC...
+
+ :attr:`base_unit` is a `AliasProperty` defaults to the unit set in
+ electrum config.
+ '''
+
+ currencies = ListProperty(['EUR', 'GBP', 'USD'])
+ '''List of currencies supported by the current exchanger plugin.
+
+ :attr:`currencies` is a `ListProperty` default to ['Eur', 'GBP'. 'USD'].
+ '''
+
+ expert_mode = BooleanProperty(False)
+ '''This defines whether expert mode options are available in the ui.
+
+ :attr:`expert_mode` is a `BooleanProperty` defaults to `False`.
+ '''
+
+ def _get_decimal(self):
+ try:
+ return self.electrum_config.get('decimal_point', 8)
+ except AttributeError:
+ return 8
+
+ def _set_decimal(self, value):
+ try:
+ self.electrum_config.set_key('decimal_point', value, True)
+ except AttributeError:
+ Logger.error('Electrum: Config not set '
+ 'While trying to save value to config')
+
+ decimal_point = AliasProperty(_get_decimal, _set_decimal)
+ '''This defines the decimal point to be used determining the
+ :attr:`decimal_point`.
+
+ :attr:`decimal_point` is a `AliasProperty` defaults to the value gotten
+ from electrum config.
'''
electrum_config = ObjectProperty(None)
@@ -61,43 +114,26 @@ class ElectrumWindow(App):
'''Number of zeros used while representing the value in base_unit.
'''
- def _get_decimal(self):
- try:
- return self.electrum_config.get('decimal_point', 8)
- except AttributeError:
- return 8
-
- def _set_decimal(self, value):
- try:
- self.electrum_config.set_key('decimal_point', value, True)
- except AttributeError:
- Logger.error('Electrum: Config not set '
- 'While trying to save value to config')
-
- decimal_point = AliasProperty(_get_decimal, _set_decimal)
- '''This defines the decimal point to be used determining the
- :attr:`base_unit`.
+ navigation_higherarchy = ListProperty([])
+ '''This is a list of the current navigation higherarchy of the app used to
+ navigate using back button.
- :attr:`decimal_point` is a `AliasProperty` defaults to the value gotten
- from electrum config.
+ :attr:`navigation_higherarchy` is s `ListProperty` defaults to []
'''
- def _get_bu(self):
- assert self.decimal_point in (5,8)
- return "BTC" if self.decimal_point == 8 else "mBTC"
+ _orientation = OptionProperty('landscape',
+ options=('landscape', 'portrait'))
- def _set_bu(self, value):
- try:
- self.electrum_config.set_key('base_unit', value, True)
- except AttributeError:
- Logger.error('Electrum: Config not set '
- 'While trying to save value to config')
+ def _get_orientation(self):
+ return self._orientation
- base_unit = AliasProperty(_get_bu, _set_bu, bind=('decimal_point',))
- '''BTC or UBTC or mBTC...
+ orientation = AliasProperty(_get_orientation,
+ None,
+ bind=('_orientation',))
+ '''Tries to ascertain the kind of device the app is running on.
+ Cane be one of `tablet` or `phone`.
- :attr:`base_unit` is a `AliasProperty` defaults to the unit set in
- electrum config.
+ :data:`orientation` is a read only `AliasProperty` Defaults to 'landscape'
'''
_ui_mode = OptionProperty('phone', options=('tablet', 'phone'))
@@ -114,51 +150,58 @@ class ElectrumWindow(App):
:data:`ui_mode` is a read only `AliasProperty` Defaults to 'phone'
'''
- _orientation = OptionProperty('landscape',
- options=('landscape', 'portrait'))
-
- def _get_orientation(self):
- return self._orientation
-
- orientation = AliasProperty(_get_orientation,
- None,
- bind=('_orientation',))
- '''Tries to ascertain the kind of device the app is running on.
- Cane be one of `tablet` or `phone`.
-
- :data:`orientation` is a read only `AliasProperty` Defaults to 'landscape'
+ url = StringProperty('', allownone=True)
+ '''
'''
- navigation_higherarchy = ListProperty([])
- '''This is a list of the current navigation higherarchy of the app used to
- navigate using back button.
+ wallet = ObjectProperty(None)
+ '''Holds the electrum wallet
- :attr:`navigation_higherarchy` is s `ListProperty` defaults to []
+ :attr:`wallet` is a `ObjectProperty` defaults to None.
'''
__events__ = ('on_back', )
def __init__(self, **kwargs):
# initialize variables
- self.info_bubble = None
+ self._clipboard = None
self.console = None
self.exchanger = None
+ self.info_bubble = None
+ self.qrscanner = None
+ self.nfcscanner = None
+ self.tabs = None
super(ElectrumWindow, self).__init__(**kwargs)
- self.network = network = kwargs.get('network')
- self.electrum_config = config = kwargs.get('config')
+ title = _('Electrum App')
+ self.network = network = kwargs.get('network', None)
+ self.electrum_config = config = kwargs.get('config', None)
+ self.gui_object = kwargs.get('gui_object', None)
+
+ self.bind(url=self.set_url)
+ # were we sent a url?
+ url = kwargs.get('url', None)
+ if url:
+ self.gui_object.set_url(url)
# create triggers so as to minimize updation a max of 2 times a sec
+ self._trigger_update_wallet =\
+ Clock.create_trigger(self.update_wallet, .5)
self._trigger_update_status =\
Clock.create_trigger(self.update_status, .5)
self._trigger_update_console =\
Clock.create_trigger(self.update_console, .5)
self._trigger_notify_transactions = \
- Clock.create_trigger(self.notify_transactions, .5)
+ Clock.create_trigger(self.notify_transactions, 5)
+
+ def set_url(self, instance, url):
+ self.gui_object.set_url(url)
def build(self):
- from kivy.lang import Builder
+ global Builder
+ if not Builder:
+ from kivy.lang import Builder
return Builder.load_file('gui/kivy/main.kv')
def _pause(self):
@@ -172,11 +215,13 @@ class ElectrumWindow(App):
def on_start(self):
''' This is the start point of the kivy ui
'''
- Window.bind(size=self.on_size,
+ win = Window
+ win.bind(size=self.on_size,
on_keyboard=self.on_keyboard)
- Window.bind(on_key_down=self.on_key_down)
+ win.bind(on_key_down=self.on_key_down)
- # register fonts
+ # Register fonts without this you won't be able to use bold/italic...
+ # inside markup.
from kivy.core.text import Label
Label.register('Roboto',
'data/fonts/Roboto.ttf',
@@ -185,23 +230,29 @@ class ElectrumWindow(App):
'data/fonts/Roboto-Bold.ttf')
if platform == 'android':
- #
- Window.bind(keyboard_height=self.on_keyboard_height)
- self.on_size(Window, Window.size)
+ # bind to keyboard height so we can get the window contents to
+ # behave the way we want when the keyboard appears.
+ win.bind(keyboard_height=self.on_keyboard_height)
+
+ self.on_size(win, win.size)
config = self.electrum_config
storage = WalletStorage(config)
Logger.info('Electrum: Check for existing wallet')
- if not storage.file_exists:
+
+ if storage.file_exists:
+ wallet = Wallet(storage)
+ action = wallet.get_action()
+ else:
+ action = 'new'
+
+ if action is not None:
# start installation wizard
Logger.debug('Electrum: Wallet not found. Launching install wizard')
- import installwizard
- wizard = installwizard.InstallWizard(config, self.network,
- storage)
+ wizard = Factory.InstallWizard(config, self.network, storage)
wizard.bind(on_wizard_complete=self.on_wizard_complete)
- wizard.run()
+ wizard.run(action)
else:
- wallet = Wallet(storage)
wallet.start_threads(self.network)
self.on_wizard_complete(None, wallet)
@@ -220,15 +271,22 @@ class ElectrumWindow(App):
# capture back button and pause app.
self._pause()
- def on_keyboard_height(self, *l):
- from kivy.animation import Animation
- from kivy.uix.popup import Popup
- active_widg = Window.children[0]
- active_widg = active_widg\
- if (active_widg == self.root or\
- issubclass(active_widg.__class__, Popup)) else\
- Window.children[1]
- Animation(y=Window.keyboard_height, d=.1).start(active_widg)
+ def on_keyboard_height(self, window, height):
+ win = window
+ active_widg = win.children[0]
+ if not issubclass(active_widg.__class__, Factory.Popup):
+ try:
+ active_widg = self.root.children[0]
+ except IndexError:
+ return
+
+ try:
+ fw = self._focused_widget
+ except AttributeError:
+ return
+ if height > 0 and fw.to_window(*fw.pos)[1] > height:
+ return
+ Factory.Animation(y=win.keyboard_height, d=.1).start(active_widg)
def on_key_down(self, instance, key, keycode, codepoint, modifiers):
if 'ctrl' in modifiers:
@@ -261,6 +319,7 @@ class ElectrumWindow(App):
def on_wizard_complete(self, instance, wallet):
if not wallet:
Logger.debug('Electrum: No Wallet set/found. Exiting...')
+ app = App.get_running_app()
app.show_error('Electrum: No Wallet set/found. Exiting...',
exit=True)
@@ -271,21 +330,6 @@ class ElectrumWindow(App):
self.load_wallet(wallet)
- #TODO: URI handling
- #self.windows.append(w)
- #if url: w.set_url(url)
-
- # TODO:remove properties are used instead
- #Clock.schedule_interval(self.timer_actions, .5)
-
-
- #TODO: remove not needed properties allow on_property events
- #def timer_actions(self):
- # if self.need_update.is_set():
- # self.update_wallet()
- # self.need_update.clear()
- # run_hook('timer_actions')
-
def init_ui(self):
''' Initialize The Ux part of electrum. This function performs the basic
tasks of setting up the ui.
@@ -297,49 +341,70 @@ class ElectrumWindow(App):
#self._tray_icon = 'icons/" + (electrum_dark_icon.png'\
# if platform == 'mac' else 'electrum_light_icon.png')
- #setup tray
+ #setup tray TODO: use the systray branch
#self.tray = SystemTrayIcon(self.icon, self)
#self.tray.setToolTip('Electrum')
#self.tray.activated.connect(self.tray_activated)
+ global ref
+ if not ref:
+ from weakref import ref
+
set_language(self.electrum_config.get('language'))
self.funds_error = False
self.completions = []
# setup UX
- self.screens = ['mainscreen']
- self.load_screen(index=0)
-
- self.icon = "icons/electrum.png"
+ self.screens = ['mainscreen',]
+
+ #setup lazy imports for mainscreen
+ Factory.register('AnimatedPopup',
+ module='electrum_gui.kivy.uix.dialogs')
+ Factory.register('TabbedCarousel',
+ module='electrum_gui.kivy.uix.screens')
+ Factory.register('ScreenDashboard',
+ module='electrum_gui.kivy.uix.screens')
+ Factory.register('EffectWidget',
+ module='electrum_gui.kivy.uix.effectwidget')
# load and focus the ui
+ #Load mainscreen
+
+ Factory.register('QRCodeWidget',
+ module='electrum_gui.kivy.uix.qrcodewidget')
+ Factory.register('MainScreen',
+ module='electrum_gui.kivy.uix.screens')
+ Factory.register('CSpinner',
+ module='electrum_gui.kivy.uix.screens')
+
+ dr = Builder.load_file('gui/kivy/uix/ui_screens/mainscreen.kv')
+ self.root.add_widget(dr)
+ self.root.manager = manager = dr.ids.manager
+ self.root.main_screen = m = manager.screens[0]
+ self.tabs = m.ids.tabs
+
+ #TODO
+ # load left_menu
+
+ self.icon = "icons/electrum.png"
# connect callbacks
if self.network:
- self.network.register_callback(
- 'updated', self._trigger_update_status)
- self.network.register_callback(
- 'banner', self._trigger_update_console)
- self.network.register_callback(
- 'disconnected', self._trigger_update_status)
- self.network.register_callback(
- 'disconnecting', self._trigger_update_status)
- self.network.register_callback('new_transaction',
- self._trigger_notify_transactions)
+ self.network.register_callback('updated', self._trigger_update_wallet)
+ #self.network.register_callback('banner', self.console.show_message(self.network.banner))
+ self.network.register_callback('disconnected', self._trigger_update_status)
+ self.network.register_callback('disconnecting', self._trigger_update_status)
+ self.network.register_callback('new_transaction', self._trigger_notify_transactions)
# set initial message
- self.update_console()
+ #self.console.show_message(self.network.banner)
self.wallet = None
def create_quote_text(self, btc_balance, mode='normal'):
'''
'''
- if not self.exchanger:
- from electrum_gui.kivy.plugins.exchange_rate import Exchanger
- self.exchanger = Exchanger(self)
- self.exchanger.start()
quote_currency = self.exchanger.currency
quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
@@ -348,19 +413,60 @@ class ElectrumWindow(App):
quote_currency)
if quote_balance is None:
- quote_text = ""
+ quote_text = u"..."
else:
- quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
+ quote_text = u"%s%.2f" % (quote_currency,
+ quote_balance)
return quote_text
def set_currencies(self, quote_currencies):
- self._trigger_update_status()
- print quote_currencies
self.currencies = sorted(quote_currencies.keys())
+ self._trigger_update_status()
+
+ def get_history_rate(self, item, btc_balance, mintime):
+ '''Historical rates: currently only using coindesk by default.
+ '''
+ maxtime = datetime.datetime.now().strftime('%Y-%m-%d')
+ rate = self.exchanger.get_history_rate(item, btc_balance, mintime,
+ maxtime)
+
+ return self.set_history_rate(item, rate)
+
+ def set_history_rate(self, item, rate):
+ '''
+ '''
+ #TODO: fix me allow other currencies to be used for history rates
+ quote_currency = self.exchanger.symbols.get('USD', 'USD')
+
+ if rate is None:
+ quote_text = "..."
+ else:
+ quote_text = "{0}{1:.3}".format(quote_currency, rate)
+
+ item = item()
+ if item:
+ item.quote_text = quote_text
+ return quote_text
def update_console(self, *dt):
- if self.console:
- self.console.showMessage(self.network.banner)
+ console = self.console
+ if console:
+ console = self.console
+ console.history = self.config.get("console-history",[])
+ console.history_index = len(console.history)
+
+ console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
+ console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
+
+ c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
+ methods = {}
+ def mkfunc(f, method):
+ return lambda *args: apply( f, (method, args, self.password_dialog ))
+ for m in dir(c):
+ if m[0]=='_' or m in ['network','wallet']: continue
+ methods[m] = mkfunc(c._run, m)
+
+ console.updateNamespace(methods)
def load_wallet(self, wallet):
self.wallet = wallet
@@ -375,25 +481,28 @@ class ElectrumWindow(App):
self.update_wallet()
# Once GUI has been initialized check if we want to announce something
# since the callback has been called before the GUI was initialized
+ self.update_history_tab()
self.notify_transactions()
self.update_account_selector()
- #TODO
- #self.new_account.setEnabled(self.wallet.seed_version>4)
- #self.update_lock_icon()
- #self.update_buttons_on_seed()
#run_hook('load_wallet', wallet)
def update_status(self, *dt):
if not self.wallet:
return
+
+ global Decimal
+ if not Decimal:
+ from decimal import Decimal
+
+ unconfirmed = ''
+ quote_text = ''
+
if self.network is None or not self.network.is_running():
text = _("Offline")
#icon = QIcon(":icons/status_disconnected.png")
elif self.network.is_connected():
- unconfirmed = ''
- quote_text = '.'
if not self.wallet.up_to_date:
text = _("Synchronizing...")
#icon = QIcon(":icons/status_waiting.png")
@@ -406,7 +515,8 @@ class ElectrumWindow(App):
if u:
unconfirmed = " [%s unconfirmed]"\
%( self.format_amount(u, True).strip())
- quote_text = self.create_quote_text(Decimal(c+u)/100000000) or '.'
+ quote_text = self.create_quote_text(Decimal(c+u)/100000000,
+ mode='symbol') or ''
#r = {}
#run_hook('set_quote_text', c+u, r)
@@ -420,29 +530,42 @@ class ElectrumWindow(App):
text = _("Not connected")
#icon = QIcon(":icons/status_disconnected.png")
- #TODO
- #status_card = self.root.main_screen.ids.tabs.ids.\
- # screen_dashboard.ids.status_card
+ try:
+ status_card = self.root.main_screen.ids.tabs.ids.\
+ screen_dashboard.ids.status_card
+ except AttributeError:
+ return
self.status = text.strip()
- #status_card.quote_text = quote_text.strip()
- #status_card.uncomfirmed = unconfirmed.strip()
- ##app.base_unit = self.base_unit().strip()
+ status_card.quote_text = quote_text.strip()
+ status_card.uncomfirmed = unconfirmed.strip()
def format_amount(self, x, is_diff=False, whitespaces=False):
'''
'''
- return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
-
- def update_wallet(self):
+ global format_satoshis
+ if not format_satoshis:
+ from electrum.wallet import format_satoshis
+ return format_satoshis(x, is_diff, self.num_zeros,
+ self.decimal_point, whitespaces)
+
+ def read_amount(self, x):
+ if x in['.', '']:
+ return None
+ p = pow(10, self.decimal_point)
+ return int( p * Decimal(x) )
+
+ def update_wallet(self, *dt):
'''
'''
- self.update_status()
- if (self.wallet.up_to_date or
- not self.network or not self.network.is_connected()):
- #TODO
- #self.update_history_tab()
- #self.update_receive_tab()
- #self.update_contacts_tab()
+ if not self.exchanger:
+ from electrum_gui.kivy.plugins.exchange_rate import Exchanger
+ self.exchanger = Exchanger(self)
+ self.exchanger.start()
+ return
+ self._trigger_update_status()
+ if (self.wallet.up_to_date or not self.network or not self.network.is_connected()):
+ self.update_history_tab()
+ self.update_contacts_tab()
self.update_completions()
def update_account_selector(self):
@@ -458,54 +581,54 @@ class ElectrumWindow(App):
else:
self.account_selector.hide()
- def update_history_tab(self, see_all=False):
- def parse_histories(items):
- results = []
- for item in items:
- tx_hash, conf, is_mine, value, fee, balance, timestamp = item
- if conf > 0:
- try:
- time_str = datetime.datetime.fromtimestamp(
- timestamp).isoformat(' ')[:-3]
- except:
- time_str = _("unknown")
-
- if conf == -1:
- time_str = _('unverified')
- icon = "atlas://gui/kivy/theming/light/close"
- elif conf == 0:
- time_str = _('pending')
- icon = "atlas://gui/kivy/theming/light/unconfirmed"
- elif conf < 6:
- time_str = '' # add new to fix error when conf < 0
- conf = max(1, conf)
- icon = "atlas://gui/kivy/theming/light/clock{}".format(conf)
- else:
- icon = "atlas://gui/kivy/theming/light/confirmed"
+ def parse_histories(self, items):
+ for item in items:
+ tx_hash, conf, is_mine, value, fee, balance, timestamp = item
+ time_str = _("unknown")
+ if conf > 0:
+ try:
+ time_str = datetime.datetime.fromtimestamp(
+ timestamp).isoformat(' ')[:-3]
+ except Exception:
+ time_str = _("error")
+
+ if conf == -1:
+ time_str = _('unverified')
+ icon = "atlas://gui/kivy/theming/light/close"
+ elif conf == 0:
+ time_str = _('pending')
+ icon = "atlas://gui/kivy/theming/light/unconfirmed"
+ elif conf < 6:
+ time_str = '' # add new to fix error when conf < 0
+ conf = max(1, conf)
+ icon = "atlas://gui/kivy/theming/light/clock{}".format(conf)
+ else:
+ icon = "atlas://gui/kivy/theming/light/confirmed"
- if value is not None:
- v_str = self.format_amount(value, True, whitespaces=True)
- else:
- v_str = '--'
+ if value is not None:
+ v_str = self.format_amount(value, True, whitespaces=True)
+ else:
+ v_str = '--'
- balance_str = self.format_amount(balance, whitespaces=True)
+ balance_str = self.format_amount(balance, whitespaces=True)
- if tx_hash:
- label, is_default_label = self.wallet.get_label(tx_hash)
- else:
- label = _('Pruned transaction outputs')
- is_default_label = False
+ if tx_hash:
+ label, is_default_label = self.wallet.get_label(tx_hash)
+ else:
+ label = _('Pruned transaction outputs')
+ is_default_label = False
- results.append((
- conf, icon, time_str, label, v_str, balance_str, tx_hash))
+ yield (conf, icon, time_str, label, v_str, balance_str, tx_hash)
- return results
+ def update_history_tab(self, see_all=False):
- history_card = self.root.main_screen.ids.tabs.ids.\
+ try:
+ history_card = self.root.main_screen.ids.tabs.ids.\
screen_dashboard.ids.recent_activity_card
- histories = parse_histories(reversed(
+ except AttributeError:
+ return
+ histories = self.parse_histories(reversed(
self.wallet.get_tx_history(self.current_account)))
- #history_view.content_adapter.data = histories
# repopulate History Card
last_widget = history_card.ids.content.children[-1]
@@ -513,26 +636,34 @@ class ElectrumWindow(App):
history_add = history_card.ids.content.add_widget
history_add(last_widget)
RecentActivityItem = Factory.RecentActivityItem
-
- history_card.ids.btn_see_all.opacity = (0 if see_all or
- len(histories) < 8 else 1)
- if not see_all:
- histories = histories[:8]
-
- create_quote_text = self.create_quote_text
+ global Decimal, ref
+ if not ref:
+ from weakref import ref
+ if not Decimal:
+ from decimal import Decimal
+
+ get_history_rate = self.get_history_rate
+ count = 0
for items in histories:
+ count += 1
conf, icon, date_time, address, amount, balance, tx = items
ri = RecentActivityItem()
ri.icon = icon
ri.date = date_time
+ mintimestr = date_time.split()[0]
ri.address = address
- ri.amount = amount
- ri.quote_text = create_quote_text(
- Decimal(amount)/100000000, mode='symbol')
+ ri.amount = amount.strip()
+ ri.quote_text = get_history_rate(ref(ri),
+ Decimal(amount),
+ mintimestr)
ri.balance = balance
ri.confirmations = conf
ri.tx_hash = tx
history_add(ri)
+ if count == 8 and not see_all:
+ break
+
+ history_card.ids.btn_see_all.opacity = (0 if count < 8 else 1)
def update_receive_tab(self):
#TODO move to address managment
@@ -590,24 +721,45 @@ class ElectrumWindow(App):
receive_list.content_adapter.data = data
def update_contacts_tab(self):
- data = []
+ contact_list = self.root.main_screen.ids.tabs.ids.\
+ screen_contacts.ids.contact_container
+ #contact_list.clear_widgets()
+
+ child = -1
+ children = contact_list.children
for address in self.wallet.addressbook:
label = self.wallet.labels.get(address, '')
- item = (address, label, "%d" % self.wallet.get_num_tx(address))
- data.append(item)
- # item.setFont(0, QFont(MONOSPACE_FONT))
- # # 32 = label can be edited (bool)
- # item.setData(0,32, True)
- # # 33 = payto string
- # item.setData(0,33, address)
+ child += 1
+ try:
+ if children[child].label == label:
+ continue
+ except IndexError:
+ pass
+ tx = self.wallet.get_num_tx(address)
+ ci = Factory.ContactItem()
+ ci.address = address
+ ci.label = label
+ ci.tx_amount = tx
+ contact_list.add_widget(ci)
+
+ #self.run_hook('update_contacts_tab')
+
+ def set_pay_from(self, l):
+ #TODO
+ return
+ self.pay_from = l
+ self.from_list.clear()
+ self.from_label.setHidden(len(self.pay_from) == 0)
+ self.from_list.setHidden(len(self.pay_from) == 0)
+ for addr in self.pay_from:
+ c, u = self.wallet.get_addr_balance(addr)
+ balance = self.format_amount(c + u)
+ self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
- self.run_hook('update_contacts_tab')
- contact_list = app.root.main_screen.ids.tabs.ids.\
- screen_contacts.ids.contacts_list
- contact_list.content_adapter.data = data
def update_completions(self):
+ #TODO: check and remove if not used
l = []
for addr, label in self.wallet.labels.items():
if addr in self.wallet.addressbook:
@@ -616,19 +768,134 @@ class ElectrumWindow(App):
#self.run_hook('update_completions', l)
self.completions = l
+ def protected(func):
+ return lambda s, *args, **kwargs: s.do_protect(func, args, **kwargs)
+
+ def do_protect(self, func, **kwargs):
+ print kwargs
+ instance = kwargs.get('instance', None)
+ password = kwargs.get('password', None)
+ message = kwargs.get('message', '')
+
+ def run_func(instance=None, password=None):
+ args = (self, instance, password)
+ apply(func, args)
+
+ if self.wallet.use_encryption:
+ return self.password_required_dialog(post_ok=run_func, message=message)
+
+ return run_func()
+
+ def do_send(self):
+ app = App.get_running_app()
+ screen_send = app.root.main_screen.ids.tabs.ids.screen_send
+ scrn = screen_send.content.ids
+ label = unicode(scrn.message_e.text)
+ # TODO
+ #if self.gui_object.payment_request:
+ # outputs = self.gui_object.payment_request.outputs
+ # amount = self.gui_object.payment_request.get_amount()
+ #else:
+
+ r = unicode(scrn.payto_e.text).strip()
+
+ # label or alias, with address in brackets
+ global re
+ if not re:
+ import re
+ m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
+ to_address = m.group(2) if m else r
+
+ global is_valid
+ if not is_valid:
+ from electrum.bitcoin import is_valid
+
+ if not is_valid(to_address):
+ app.show_error(_('Invalid Bitcoin Address') +
+ ':\n' + to_address)
+ return
+
+ try:
+ amount = self.read_amount(unicode(scrn.amount_e.text))
+ except Exception:
+ app.show_error(_('Invalid Amount'))
+ return
+ try:
+ fee = self.read_amount(unicode(scrn.fee_e.amt))
+ except Exception as err:
+ print err
+ app.show_error(_('Invalid Fee'))
+ return
+
+ from pudb import set_trace; set_trace()
+ message = 'sending {} {} to {}'.format(\
+ app.base_unit, scrn.amount_e.text, r)
+
+ confirm_fee = self.config.get('confirm_fee', 100000)
+ if fee >= confirm_fee:
+ if not self.question(_("The fee for this transaction seems unusually high.\nAre you really sure you want to pay %(fee)s in fees?")%{ 'fee' : self.format_amount(fee) + ' '+ self.base_unit()}):
+ return
+
+ self.send_tx(to_address, amount, fee, label)
+
+ @protected
+ def send_tx(self, outputs, fee, label, password):
+
+ # first, create an unsigned tx
+ domain = self.get_payment_sources()
+ try:
+ tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
+ tx.error = None
+ except Exception as e:
+ traceback.print_exc(file=sys.stdout)
+ self.show_info(str(e))
+ return
+
+ # call hook to see if plugin needs gui interaction
+ #run_hook('send_tx', tx)
+
+ # sign the tx
+ def sign_thread():
+ time.sleep(0.1)
+ keypairs = {}
+ self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
+ self.wallet.sign_transaction(tx, keypairs, password)
+ return tx, fee, label
+
+ def sign_done(tx, fee, label):
+ if tx.error:
+ self.show_info(tx.error)
+ return
+ if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
+ self.show_error(_("This transaction requires a higher fee, or "
+ "it will not be propagated by the network."))
+ return
+ if label:
+ self.wallet.set_label(tx.hash(), label)
+
+ if not self.gui_object.payment_request:
+ if not tx.is_complete() or self.config.get('show_before_broadcast'):
+ self.show_transaction(tx)
+ return
+
+ self.broadcast_transaction(tx)
+
+ WaitingDialog(self, 'Signing..').start(sign_thread, sign_done)
+
def notify_transactions(self, *dt):
'''
'''
if not self.network or not self.network.is_connected():
return
- iface = self.network.interface
- if len(iface.pending_transactions_for_notifications) > 0:
+ iface = self.network
+ ptfn = iface.pending_transactions_for_notifications
+ if len(ptfn) > 0:
# Combine the transactions if there are more then three
- tx_amount = len(iface.pending_transactions_for_notifications)
+ tx_amount = len(ptfn)
if(tx_amount >= 3):
total_amount = 0
- for tx in iface.pending_transactions_for_notifications:
+ for tx in ptfn:
is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
if(v > 0):
total_amount += v
@@ -645,11 +912,18 @@ class ElectrumWindow(App):
iface.pending_transactions_for_notifications.remove(tx)
is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
if(v > 0):
- from pudb import set_trace; set_trace()
self.notify(
- _("New transaction received. {amount}s {unit}s").
+ _("{} new transaction received. {amount}s {unit}s").
format( amount=self.format_amount(v),
- unit=self.base_unit()))
+ unit=self.base_unit))
+
+ def copy(self, text):
+ ''' Copy provided text to clipboard
+ '''
+ if not self._clipboard:
+ from kivy.core.clipboard import Clipboard
+ self._clipboard = Clipboard
+ self._clipboard.put(text, 'text/plain')
def notify(self, message):
try:
@@ -668,31 +942,42 @@ class ElectrumWindow(App):
'''
'''
# pause nfc
- # pause qrscanner(Camera) if active
+ if self.qrscanner:
+ self.qrscanner.stop()
+ if self.nfcscanner:
+ self.nfcscanner.nfc_disable()
return True
def on_resume(self):
'''
'''
- # resume nfc
- # resume camera if active
- pass
+ if self.qrscanner and qrscanner.get_parent_window():
+ self.qrscanner.start()
+ if self.nfcscanner:
+ self.nfcscanner.nfc_enable()
def on_size(self, instance, value):
width, height = value
self._orientation = 'landscape' if width > height else 'portrait'
+
+ global inch
+ if not inch:
+ from kivy.metrics import inch
+
self._ui_mode = 'tablet' if min(width, height) > inch(3.51) else 'phone'
Logger.debug('orientation: {} ui_mode: {}'.format(self._orientation,
self._ui_mode))
- def load_screen(self, index=0, direction='left', manager=None):
+ def load_screen(self, index=0, direction='left', manager=None, switch=True):
''' Load the appropriate screen as mentioned in the parameters.
'''
manager = manager or self.root.manager
- screen = Builder.load_file('gui/kivy/ui_screens/'\
+ screen = Builder.load_file('gui/kivy/uix/ui_screens/'\
+ self.screens[index] + '.kv')
screen.name = self.screens[index]
- manager.switch_to(screen, direction=direction)
+ if switch:
+ manager.switch_to(screen, direction=direction)
+ return screen
def load_next_screen(self):
'''
@@ -705,7 +990,7 @@ class ElectrumWindow(App):
self.load_screen()
def load_previous_screen(self):
- '''
+ ''' Load the previous screen from disk.
'''
manager = root.manager
try:
@@ -715,51 +1000,239 @@ class ElectrumWindow(App):
except IndexError:
pass
- def show_error(self, error,
- width='200dp',
- pos=None,
- arrow_pos=None,
- exit=False,
- icon='atlas://gui/kivy/theming/light/error',
- duration=0,
- modal=False):
+ def save_new_contact(self, address, label):
+ address = unicode(address)
+ label = unicode(label)
+ global is_valid
+ if not is_valid:
+ from electrum.bitcoin import is_valid
+
+
+ if is_valid(address):
+ if label:
+ self.set_label(address, text=label)
+ self.wallet.add_contact(address)
+ self.update_contacts_tab()
+ self.update_history_tab()
+ self.update_completions()
+ else:
+ self.show_error(_('Invalid Address'))
+
+ def send_payment(self, address, amount=0, label='', message=''):
+ tabs = self.tabs
+ screen_send = tabs.ids.screen_send
+
+ if label and self.wallet.labels.get(address) != label:
+ #if self.question('Give label "%s" to address %s ?'%(label,address)):
+ if address not in self.wallet.addressbook and not self.wallet. is_mine(address):
+ self.wallet.addressbook.append(address)
+ self.wallet.set_label(address, label)
+
+ # switch_to the send screen
+ tabs.ids.panel.switch_to(tabs.ids.tab_send)
+
+ label = self.wallet.labels.get(address)
+ m_addr = label + ' <'+ address +'>' if label else address
+
+ # populate
+ def set_address(*l):
+ content = screen_send.content.ids
+ content.payto_e.text = m_addr
+ content.message_e.text = message
+ if amount:
+ content.amount_e.text = amount
+
+ # wait for screen to load
+ Clock.schedule_once(set_address, .5)
+
+ def set_send(self, address, amount, label, message):
+ self.send_payment(address, amount=amount, label=label, message=message)
+
+ def prepare_for_payment_request(self):
+ tabs = self.tabs
+ screen_send = tabs.ids.screen_send
+
+ # switch_to the send screen
+ tabs.ids.panel.switch_to(tabs.ids.tab_send)
+
+ content = screen_send.content.ids
+ if content:
+ self.set_frozen(content, False)
+ screen_send.screen_label.text = _("please wait...")
+ return True
+
+ def payment_request_ok(self):
+ tabs = self.tabs
+ screen_send = tabs.ids.screen_send
+
+ # switch_to the send screen
+ tabs.ids.panel.switch_to(tabs.ids.tab_send)
+
+ content = screen_send.content
+ self.set_frozen(content, True)
+
+ content.ids.payto_e.text = self.gui_object.payment_request.domain
+ content.ids.amount_e.text = self.format_amount(self.gui_object.payment_request.get_amount())
+ content.ids.message_e.text = self.gui_object.payment_request.memo
+
+ # wait for screen to load
+ Clock.schedule_once(set_address, .5)
+
+ def do_clear(self):
+ tabs = self.tabs
+ screen_send = tabs.ids.screen_send
+ content = screen_send.ids.content
+ cts = content.ids
+ cts.payto_e.text = cts.message_e.text = cts.amount_e.text = \
+ cts.fee_e.text = ''
+
+ self.set_frozen(content, False)
+
+ self.set_pay_from([])
+ self.update_status()
+
+ def set_frozen(self, entry, frozen):
+ if frozen:
+ entry.disabled = True
+ Factory.Animation(opacity=0).start(content)
+ else:
+ entry.disabled = False
+ Factory.Animation(opacity=1).start(content)
+
+ def set_addrs_frozen(self,addrs,freeze):
+ for addr in addrs:
+ if not addr: continue
+ if addr in self.wallet.frozen_addresses and not freeze:
+ self.wallet.unfreeze(addr)
+ elif addr not in self.wallet.frozen_addresses and freeze:
+ self.wallet.freeze(addr)
+ self.update_receive_tab()
+
+ def payment_request_error(self):
+ tabs = self.tabs
+ screen_send = tabs.ids.screen_send
+
+ # switch_to the send screen
+ tabs.ids.panel.switch_to(tabs.ids.tab_send)
+
+ self.do_clear()
+ self.show_info(self.gui_object.payment_request.error)
+
+ def encode_uri(self, addr, amount=0, label='',
+ message='', size='', currency='btc'):
+ ''' Convert to BIP0021 compatible URI
+ '''
+ uri = 'bitcoin:{}'.format(addr)
+ first = True
+ if amount:
+ uri += '{}amount={}'.format('?' if first else '&', amount)
+ first = False
+ if label:
+ uri += '{}label={}'.format('?' if first else '&', label)
+ first = False
+ if message:
+ uri += '{}?message={}'.format('?' if first else '&', message)
+ first = False
+ if size:
+ uri += '{}size={}'.format('?' if not first else '&', size)
+ return uri
+
+ def decode_uri(self, uri):
+ if ':' not in uri:
+ # It's just an address (not BIP21)
+ return {'address': uri}
+
+ if '//' not in uri:
+ # Workaround for urlparse, it don't handle bitcoin: URI properly
+ uri = uri.replace(':', '://')
+
+ try:
+ uri = urlparse(uri)
+ except NameError:
+ # delayed import
+ from urlparse import urlparse, parse_qs
+ uri = urlparse(uri)
+
+ result = {'address': uri.netloc}
+
+ if uri.path.startswith('?'):
+ params = parse_qs(uri.path[1:])
+ else:
+ params = parse_qs(uri.path)
+
+ for k,v in params.items():
+ if k in ('amount', 'label', 'message', 'size'):
+ result[k] = v[0]
+
+ return result
+
+ def delete_imported_key(self, addr):
+ self.wallet.delete_imported_key(addr)
+ self.update_receive_tab()
+ self.update_history_tab()
+
+ def delete_pending_account(self, k):
+ self.wallet.delete_pending_account(k)
+ self.update_receive_tab()
+
+ def get_sendable_balance(self):
+ return sum(sum(self.wallet.get_addr_balance(a))
+ for a in self.get_payment_sources())
+
+
+ def get_payment_sources(self):
+ if self.pay_from:
+ return self.pay_from
+ else:
+ return self.wallet.get_account_addresses(self.current_account)
+
+
+ def send_from_addresses(self, addrs):
+ self.set_pay_from( addrs )
+ tabs = self.tabs
+ screen_send = tabs.ids.screen_send
+ self.tabs.setCurrentIndex(1)
+
+
+ def payto(self, addr):
+ if not addr:
+ return
+ label = self.wallet.labels.get(addr)
+ m_addr = label + ' <' + addr + '>' if label else addr
+ self.tabs.setCurrentIndex(1)
+ self.payto_e.setText(m_addr)
+ self.amount_e.setFocus()
+
+
+ def delete_contact(self, x):
+ if self.question(_("Do you want to remove") +
+ " %s "%x +
+ _("from your list of contacts?")):
+ self.wallet.delete_contact(x)
+ self.wallet.set_label(x, None)
+ self.update_history_tab()
+ self.update_contacts_tab()
+ self.update_completions()
+
+ def show_error(self, error, width='200dp', pos=None, arrow_pos=None,
+ exit=False, icon='atlas://gui/kivy/theming/light/error', duration=0,
+ modal=False):
''' Show a error Message Bubble.
'''
- self.show_info_bubble(
- text=error,
- icon=icon,
- width=width,
- pos=pos or Window.center,
- arrow_pos=arrow_pos,
- exit=exit,
- duration=duration,
- modal=modal)
-
- def show_info(self, error,
- width='200dp',
- pos=None,
- arrow_pos=None,
- exit=False,
- duration=0,
- modal=False):
+ self.show_info_bubble( text=error, icon=icon, width=width,
+ pos=pos or Window.center, arrow_pos=arrow_pos, exit=exit,
+ duration=duration, modal=modal)
+
+ def show_info(self, error, width='200dp', pos=None, arrow_pos=None,
+ exit=False, duration=0, modal=False):
''' Show a Info Message Bubble.
'''
self.show_error(error, icon='atlas://gui/kivy/theming/light/error',
- duration=duration,
- modal=modal,
- exit=exit,
- pos=pos,
- arrow_pos=arrow_pos)
-
- def show_info_bubble(self,
- text=_('Hello World'),
- pos=(0, 0),
- duration=0,
- arrow_pos='bottom_mid',
- width=None,
- icon='',
- modal=False,
- exit=False):
+ duration=duration, modal=modal, exit=exit, pos=pos,
+ arrow_pos=arrow_pos)
+
+ def show_info_bubble(self, text=_('Hello World'), pos=None, duration=0,
+ arrow_pos='bottom_mid', width=None, icon='', modal=False, exit=False):
'''Method to show a Information Bubble
.. parameters::
@@ -769,13 +1242,13 @@ class ElectrumWindow(App):
width: width of the Bubble
arrow_pos: arrow position for the bubble
'''
-
info_bubble = self.info_bubble
if not info_bubble:
- info_bubble = self.info_bubble = InfoBubble()
+ info_bubble = self.info_bubble = Factory.InfoBubble()
+ win = Window
if info_bubble.parent:
- Window.remove_widget(info_bubble
+ win.remove_widget(info_bubble
if not info_bubble.modal else
info_bubble._modal_view)
@@ -794,7 +1267,6 @@ class ElectrumWindow(App):
info_bubble.show_arrow = False
img.allow_stretch = True
info_bubble.dim_background = True
- pos = (Window.center[0], Window.center[1] - info_bubble.center[1])
info_bubble.background_image = 'atlas://gui/kivy/theming/light/card'
else:
info_bubble.fs = False
@@ -805,4 +1277,6 @@ class ElectrumWindow(App):
info_bubble.dim_background = False
info_bubble.background_image = 'atlas://data/images/defaulttheme/bubble'
info_bubble.message = text
- info_bubble.show(pos, duration, width, modal=modal, exit=exit)
+ if not pos:
+ pos = (win.center[0], win.center[1] - (info_bubble.height/2))
+ info_bubble.show(pos, duration, width, modal=modal, exit=exit)+
\ No newline at end of file
diff --git a/gui/kivy/plugins/exchange_rate.py b/gui/kivy/plugins/exchange_rate.py
@@ -6,13 +6,18 @@ This module is responsible for getting the conversion rates from different
bitcoin exchanges.
'''
+import decimal
+import json
+
from kivy.network.urlrequest import UrlRequest
from kivy.event import EventDispatcher
from kivy.properties import (OptionProperty, StringProperty, AliasProperty,
ListProperty)
from kivy.clock import Clock
-import decimal
-import json
+from kivy.cache import Cache
+
+# Register local cache
+Cache.register('history_rate', timeout=220)
EXCHANGES = ["BitcoinAverage",
"BitcoinVenezuela",
@@ -25,27 +30,32 @@ EXCHANGES = ["BitcoinAverage",
"LocalBitcoins",
"Winkdex"]
+HISTORY_EXCHNAGES = ['Coindesk',
+ 'Winkdex',
+ 'BitcoinVenezuela']
+
class Exchanger(EventDispatcher):
''' Provide exchanges rate between crypto and different national
currencies. See Module Documentation for details.
'''
- symbols = {'ALL': 'Lek', 'AED': 'د.إ', 'AFN':'؋', 'ARS': '$', 'AMD': '֏',
- 'AWG': 'ƒ', 'ANG': 'ƒ', 'AOA': 'Kz', 'BDT': '৳', 'BHD': 'BD',
- 'BIF': 'FBu', 'BTC': 'BTC', 'BTN': 'Nu', 'CDF': 'FC', 'CHF': 'CHF',
- 'CLF': 'UF', 'CLP':'$', 'CVE': '$', 'DJF':'Fdj', 'DZD': 'دج',
- 'AUD': '$', 'AZN': 'ман', 'BSD': '$', 'BBD': '$', 'BYR': 'p', 'CRC': '₡',
- 'BZD': 'BZ$', 'BMD': '$', 'BOB': '$b', 'BAM': 'KM', 'BWP': 'P',
- 'BGN': 'лв', 'BRL': 'R$', 'BND': '$', 'KHR': '៛', 'CAD': '$',
- 'ERN': 'Nfk', 'ETB': 'Br', 'KYD': '$', 'USD': '$', 'CLP': '$',
- 'HRK': 'kn', 'CUP':'₱', 'CZK': 'Kč', 'DKK': 'kr', 'DOP': 'RD$',
- 'XCD': '$', 'EGP': '£', 'SVC': '$' , 'EEK': 'kr', 'EUR': '€',
- 'FKP': '£', 'FJD': '$', 'GHC': '¢', 'GIP': '£', 'GTQ': 'Q', 'GBP': '£',
- 'GYD': '$', 'HNL': 'L', 'HKD': '$', 'HUF': 'Ft', 'ISK': 'kr',
- 'INR': '₹', 'IDR': 'Rp', 'IRR': '﷼', 'IMP': '£', 'ILS': '₪', 'COP': '$',
- 'JMD': 'J$', 'JPY': '¥', 'JEP': '£', 'KZT': 'лв', 'KPW': '₩',
- 'KRW': '₩', 'KGS': 'лв', 'LAK': '₭', 'LVL': 'Ls', 'CNY': '¥'}
+ symbols = {'ALL': u'Lek', 'AED': u'د.إ', 'AFN':u'؋', 'ARS': u'$',
+ 'AMD': u'֏', 'AWG': u'ƒ', 'ANG': u'ƒ', 'AOA': u'Kz', 'BDT': u'৳',
+ 'BHD': u'BD', 'BIF': u'FBu', 'BTC': u'BTC', 'BTN': u'Nu', 'CDF': u'FC',
+ 'CHF': u'CHF', 'CLF': u'UF', 'CLP':u'$', 'CVE': u'$', 'DJF':u'Fdj',
+ 'DZD': u'دج', 'AUD': u'$', 'AZN': u'ман', 'BSD': u'$', 'BBD': u'$',
+ 'BYR': u'p', 'CRC': u'₡', 'BZD': u'BZ$', 'BMD': u'$', 'BOB': u'$b',
+ 'BAM': u'KM', 'BWP': u'P', 'BGN': 'uлв', 'BRL': u'R$', 'BND': u'$',
+ 'KHR': u'៛', 'CAD': u'$', 'ERN': u'Nfk', 'ETB': u'Br', 'KYD': u'$',
+ 'USD': u'$', 'CLP': u'$', 'HRK': u'kn', 'CUP': u'₱', 'CZK': u'Kč',
+ 'DKK': u'kr', 'DOP': u'RD$', 'XCD': u'$', 'EGP': u'£', 'SVC': u'$' ,
+ 'EEK': u'kr', 'EUR': u'€', u'FKP': u'£', 'FJD': u'$', 'GHC': u'¢',
+ 'GIP': u'£', 'GTQ': u'Q', 'GBP': u'£', 'GYD': u'$', 'HNL': u'L',
+ 'HKD': u'$', 'HUF': u'Ft', 'ISK': u'kr', 'INR': u'₹', 'IDR': u'Rp',
+ 'IRR': u'﷼', 'IMP': '£', 'ILS': '₪', 'COP': '$', 'JMD': u'J$',
+ 'JPY': u'¥', 'JEP': u'£', 'KZT': u'лв', 'KPW': u'₩', 'KRW': u'₩',
+ 'KGS': u'лв', 'LAK': u'₭', 'LVL': u'Ls', 'CNY': u'¥'}
_use_exchange = OptionProperty('Blockchain', options=EXCHANGES)
'''This is the exchange to be used for getting the currency exchange rates
@@ -56,23 +66,16 @@ class Exchanger(EventDispatcher):
'''
def _set_currency(self, value):
- exchanger = self.exchanger
+ value = str(value)
if self.use_exchange == 'CoinDesk':
self._update_cd_currency(self.currency)
return
- try:
- self._currency = value
- self.electrum_cinfig.set_key('currency', value, True)
- except AttributeError:
- self._currency = 'EUR'
+ self._currency = value
+ self.parent.electrum_config.set_key('currency', value, True)
def _get_currency(self):
- try:
- self._currency = self.electrum_config.get('currency', 'EUR')
- except AttributeError:
- pass
- finally:
- return self._currency
+ self._currency = self.parent.electrum_config.get('currency', 'EUR')
+ return self._currency
currency = AliasProperty(_get_currency, _set_currency, bind=('_currency',))
@@ -104,6 +107,7 @@ class Exchanger(EventDispatcher):
self.parent = parent
self.quote_currencies = None
self.exchanges = EXCHANGES
+ self.history_exchanges = HISTORY_EXCHNAGES
def exchange(self, btc_amount, quote_currency):
if self.quote_currencies is None:
@@ -115,10 +119,40 @@ class Exchanger(EventDispatcher):
return btc_amount * decimal.Decimal(quote_currencies[quote_currency])
+ def get_history_rate(self, item, btc_amt, mintime, maxtime):
+ def on_success(request, response):
+ response = json.loads(response)
+
+ try:
+ hrate = response['bpi'][mintime]
+ hrate = abs(btc_amt) * decimal.Decimal(hrate)
+ Cache.append('history_rate', uid, hrate)
+ except KeyError:
+ hrate = 'not found'
+
+ self.parent.set_history_rate(item, hrate)
+
+ # Check local cache before getting data from remote
+ exchange = 'coindesk'
+ uid = '{}:{}'.format(exchange, mintime)
+ hrate = Cache.get('history_rate', uid)
+
+ if hrate:
+ return hrate
+
+ req = UrlRequest(url='https://api.coindesk.com/v1/bpi/historical'
+ '/close.json?start={}&end={}'
+ .format(mintime, maxtime)
+ ,on_success=on_success, timeout=15)
+ return None
+
def update_rate(self, dt):
''' This is called from :method:`start` every X seconds; to update the
rates for currencies for the currently selected exchange.
'''
+ if not self.parent.network or not self.parent.network.is_connected():
+ return
+
update_rates = {
"BitcoinAverage": self.update_ba,
"BitcoinVenezuela": self.update_bv,
@@ -268,7 +302,7 @@ class Exchanger(EventDispatcher):
for r in response:
quote_currencies[r] = _lookup_rate(response, r)
self.quote_currencies = quote_currencies
- except KeyError:
+ except KeyError, TypeError:
pass
self.parent.set_currencies(quote_currencies)
@@ -329,9 +363,8 @@ class Exchanger(EventDispatcher):
timeout=5)
def start(self):
- # check rates every few seconds
self.update_rate(0)
- # check every few seconds
+ # check every 20 seconds
Clock.unschedule(self.update_rate)
Clock.schedule_interval(self.update_rate, 20)
diff --git a/gui/kivy/qr_scanner/__init__.py b/gui/kivy/qr_scanner/__init__.py
@@ -7,69 +7,23 @@ from collections import namedtuple
from kivy.uix.anchorlayout import AnchorLayout
from kivy.core import core_select_lib
+from kivy.metrics import dp
from kivy.properties import ListProperty, BooleanProperty
from kivy.factory import Factory
-def encode_uri(addr, amount=0, label='', message='', size='',
- currency='btc'):
- ''' Convert to BIP0021 compatible URI
- '''
- uri = 'bitcoin:{}'.format(addr)
- first = True
- if amount:
- uri += '{}amount={}'.format('?' if first else '&', amount)
- first = False
- if label:
- uri += '{}label={}'.format('?' if first else '&', label)
- first = False
- if message:
- uri += '{}?message={}'.format('?' if first else '&', message)
- first = False
- if size:
- uri += '{}size={}'.format('?' if not first else '&', size)
- return uri
-
-def decode_uri(uri):
- if ':' not in uri:
- # It's just an address (not BIP21)
- return {'address': uri}
-
- if '//' not in uri:
- # Workaround for urlparse, it don't handle bitcoin: URI properly
- uri = uri.replace(':', '://')
-
- try:
- uri = urlparse(uri)
- except NameError:
- # delayed import
- from urlparse import urlparse, parse_qs
- uri = urlparse(uri)
-
- result = {'address': uri.netloc}
-
- if uri.path.startswith('?'):
- params = parse_qs(uri.path[1:])
- else:
- params = parse_qs(uri.path)
-
- for k,v in params.items():
- if k in ('amount', 'label', 'message', 'size'):
- result[k] = v[0]
-
- return result
-
-
class ScannerBase(AnchorLayout):
''' Base implementation for camera based scanner
'''
- camera_size = ListProperty([320, 240])
+ camera_size = ListProperty([320, 240] if dp(1) < 2 else [640, 480])
symbols = ListProperty([])
# XXX can't work now, due to overlay.
show_bounds = BooleanProperty(False)
+ running = BooleanProperty(False)
+
Qrcode = namedtuple('Qrcode',
['type', 'data', 'bounds', 'quality', 'count'])
diff --git a/gui/kivy/qr_scanner/scanner_android.py b/gui/kivy/qr_scanner/scanner_android.py
@@ -88,7 +88,7 @@ class SurfaceHolderCallback(PythonJavaClass):
def __init__(self, callback):
super(SurfaceHolderCallback, self).__init__()
self.callback = callback
-
+
@java_method('(Landroid/view/SurfaceHolder;III)V')
def surfaceChanged(self, surface, fmt, width, height):
self.callback(fmt, width, height)
@@ -96,7 +96,7 @@ class SurfaceHolderCallback(PythonJavaClass):
@java_method('(Landroid/view/SurfaceHolder;)V')
def surfaceCreated(self, surface):
pass
-
+
@java_method('(Landroid/view/SurfaceHolder;)V')
def surfaceDestroyed(self, surface):
pass
@@ -170,6 +170,7 @@ class AndroidCamera(Widget):
@run_on_ui_thread
def stop(self):
+ self.running = False
if self._android_camera is None:
return
self._android_camera.setPreviewCallback(None)
@@ -179,6 +180,7 @@ class AndroidCamera(Widget):
@run_on_ui_thread
def start(self):
+ self.running = True
if self._android_camera is not None:
return
@@ -196,6 +198,9 @@ class AndroidCamera(Widget):
# attach the android surfaceview to our android widget holder
self._holder.view = self._android_surface
+ # set orientation
+ self._android_camera.setDisplayOrientation(90)
+
def _on_surface_changed(self, fmt, width, height):
# internal, called when the android SurfaceView is ready
# FIXME if the size is not handled by the camera, it will failed.
diff --git a/gui/kivy/qrcodewidget.py b/gui/kivy/qrcodewidget.py
@@ -1,179 +0,0 @@
-''' Kivy Widget that accepts data and displas qrcode
-'''
-
-from threading import Thread
-from functools import partial
-
-from kivy.uix.floatlayout import FloatLayout
-
-from kivy.graphics.texture import Texture
-from kivy.properties import StringProperty
-from kivy.properties import ObjectProperty, StringProperty, ListProperty,\
- BooleanProperty
-from kivy.lang import Builder
-from kivy.clock import Clock
-
-try:
- import qrcode
-except ImportError:
- sys.exit("Error: qrcode does not seem to be installed. Try 'sudo pip install qrcode'")
-
-
-
-Builder.load_string('''
-<QRCodeWidget>
- on_parent: if args[1]: qrimage.source = self.loading_image
- canvas.before:
- # Draw white Rectangle
- Color:
- rgba: root.background_color
- Rectangle:
- size: self.size
- pos: self.pos
- canvas.after:
- Color:
- rgba: .5, .5, .5, 1 if root.show_border else 0
- Line:
- width: dp(1.333)
- points:
- dp(2), dp(2),\
- self.width - dp(2), dp(2),\
- self.width - dp(2), self.height - dp(2),\
- dp(2), self.height - dp(2),\
- dp(2), dp(2)
- Image
- id: qrimage
- pos_hint: {'center_x': .5, 'center_y': .5}
- allow_stretch: True
- size_hint: None, None
- size: root.width * .9, root.height * .9
-''')
-
-class QRCodeWidget(FloatLayout):
-
- show_border = BooleanProperty(True)
- '''Whether to show border around the widget.
-
- :data:`show_border` is a :class:`~kivy.properties.BooleanProperty`,
- defaulting to `True`.
- '''
-
- data = StringProperty(None, allow_none=True)
- ''' Data using which the qrcode is generated.
-
- :data:`data` is a :class:`~kivy.properties.StringProperty`, defaulting to
- `None`.
- '''
-
- background_color = ListProperty((1, 1, 1, 1))
- ''' Background color of the background of the widget.
-
- :data:`background_color` is a :class:`~kivy.properties.ListProperty`,
- defaulting to `(1, 1, 1, 1)`.
- '''
-
- loading_image = StringProperty('gui/kivy/theming/loading.gif')
-
- def __init__(self, **kwargs):
- super(QRCodeWidget, self).__init__(**kwargs)
- self.addr = None
- self.qr = None
- self._qrtexture = None
-
- def on_data(self, instance, value):
- if not (self.canvas or value):
- return
- img = self.ids.get('qrimage', None)
-
- if not img:
- # if texture hasn't yet been created delay the texture updation
- Clock.schedule_once(lambda dt: self.on_data(instance, value))
- return
- img.anim_delay = .25
- img.source = self.loading_image
- Thread(target=partial(self.generate_qr, value)).start()
-
- def generate_qr(self, value):
- self.set_addr(value)
- self.update_qr()
-
- def set_addr(self, addr):
- if self.addr == addr:
- return
- MinSize = 210 if len(addr) < 128 else 500
- self.setMinimumSize((MinSize, MinSize))
- self.addr = addr
- self.qr = None
-
- def update_qr(self):
- if not self.addr and self.qr:
- return
- QRCode = qrcode.QRCode
- L = qrcode.constants.ERROR_CORRECT_L
- addr = self.addr
- try:
- self.qr = qr = QRCode(
- version=None,
- error_correction=L,
- box_size=10,
- border=0,
- )
- qr.add_data(addr)
- qr.make(fit=True)
- except Exception as e:
- print e
- self.qr=None
- self.update_texture()
-
- def setMinimumSize(self, size):
- # currently unused, do we need this?
- self._texture_size = size
-
- def _create_texture(self, k, dt):
- self._qrtexture = texture = Texture.create(size=(k,k), colorfmt='rgb')
- # don't interpolate texture
- texture.min_filter = 'nearest'
- texture.mag_filter = 'nearest'
-
- def update_texture(self):
- if not self.addr:
- return
-
- matrix = self.qr.get_matrix()
- k = len(matrix)
- # create the texture in main UI thread otherwise
- # this will lead to memory corruption
- Clock.schedule_once(partial(self._create_texture, k), -1)
- buff = []
- bext = buff.extend
- cr, cg, cb, ca = self.background_color[:]
- cr, cg, cb = cr*255, cg*255, cb*255
-
- for r in range(k):
- for c in range(k):
- bext([0, 0, 0] if matrix[r][c] else [cr, cg, cb])
-
- # then blit the buffer
- buff = ''.join(map(chr, buff))
- # update texture in UI thread.
- Clock.schedule_once(lambda dt: self._upd_texture(buff))
-
- def _upd_texture(self, buff):
- texture = self._qrtexture
- if not texture:
- # if texture hasn't yet been created delay the texture updation
- Clock.schedule_once(lambda dt: self._upd_texture(buff))
- return
- texture.blit_buffer(buff, colorfmt='rgb', bufferfmt='ubyte')
- img =self.ids.qrimage
- img.anim_delay = -1
- img.texture = texture
- img.canvas.ask_update()
-
-if __name__ == '__main__':
- from kivy.app import runTouchApp
- import sys
- data = str(sys.argv[1:])
- runTouchApp(QRCodeWidget(data=data))
-
-
diff --git a/gui/kivy/screens.py b/gui/kivy/screens.py
@@ -1,105 +0,0 @@
-from kivy.app import App
-from kivy.uix.screenmanager import Screen
-from kivy.properties import ObjectProperty
-from kivy.clock import Clock
-
-
-class CScreen(Screen):
-
- __events__ = ('on_activate', 'on_deactivate')
-
- action_view = ObjectProperty(None)
-
- def _change_action_view(self):
- app = App.get_running_app()
- action_bar = app.root.manager.current_screen.ids.action_bar
- _action_view = self.action_view
-
- if (not _action_view) or _action_view.parent:
- return
- action_bar.clear_widgets()
- action_bar.add_widget(_action_view)
-
- def on_activate(self):
- Clock.schedule_once(lambda dt: self._change_action_view())
-
- def on_deactivate(self):
- Clock.schedule_once(lambda dt: self._change_action_view())
-
-
-class ScreenDashboard(CScreen):
-
- tab = ObjectProperty(None)
-
- def show_tx_details(
- self, date, address, amount, amount_color, balance,
- tx_hash, conf, quote_text):
-
- ra_dialog = RecentActivityDialog()
-
- ra_dialog.address = address
- ra_dialog.amount = amount
- ra_dialog.amount_color = amount_color
- ra_dialog.confirmations = conf
- ra_dialog.quote_text = quote_text
- date_time = date.split()
- if len(date_time) == 2:
- ra_dialog.date = date_time[0]
- ra_dialog.time = date_time[1]
- ra_dialog.status = 'Validated'
- else:
- ra_dialog.date = date_time
- ra_dialog.status = 'Pending'
- ra_dialog.tx_hash = tx_hash
-
- app = App.get_running_app()
- main_gui = app.gui.main_gui
- tx_hash = tx_hash
- tx = app.wallet.transactions.get(tx_hash)
-
- if tx_hash in app.wallet.transactions.keys():
- is_relevant, is_mine, v, fee = app.wallet.get_tx_value(tx)
- conf, timestamp = app.wallet.verifier.get_confirmations(tx_hash)
- #if timestamp:
- # time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
- #else:
- # time_str = 'pending'
- else:
- is_mine = False
-
- ra_dialog.is_mine = is_mine
-
- if is_mine:
- if fee is not None:
- ra_dialog.fee = main_gui.format_amount(fee)
- else:
- ra_dialog.fee = 'unknown'
-
- ra_dialog.open()
-
-
-class ScreenPassword(Screen):
-
- __events__ = ('on_release', 'on_deactivate', 'on_activate')
-
- def on_activate(self):
- app = App.get_running_app()
- action_bar = app.root.main_screen.ids.action_bar
- action_bar.add_widget(self._action_view)
-
- def on_deactivate(self):
- self.ids.password.text = ''
-
- def on_release(self, *args):
- pass
-
-class ScreenSend(CScreen):
- pass
-
-class ScreenReceive(CScreen):
- pass
-
-class ScreenContacts(CScreen):
-
- def add_new_contact(self):
- NewContactDialog().open()
diff --git a/gui/kivy/statusbar.py b/gui/kivy/statusbar.py
@@ -1,7 +0,0 @@
-from kivy.uix.boxlayout import BoxLayout
-from kivy.properties import StringProperty
-
-
-class StatusBar(BoxLayout):
-
- text = StringProperty('')
diff --git a/gui/kivy/textinput.py b/gui/kivy/textinput.py
@@ -1,14 +0,0 @@
-from kivy.uix.textinput import TextInput
-from kivy.properties import OptionProperty
-
-class ELTextInput(TextInput):
-
- def insert_text(self, substring, from_undo=False):
- if not from_undo:
- if self.input_type == 'numbers':
- numeric_list = map(str, range(10))
- if '.' not in self.text:
- numeric_list.append('.')
- if substring not in numeric_list:
- return
- super(ELTextInput, self).insert_text(substring, from_undo=from_undo)
diff --git a/gui/kivy/theming/light-0.png b/gui/kivy/theming/light-0.png
Binary files differ.
diff --git a/gui/kivy/theming/light.atlas b/gui/kivy/theming/light.atlas
@@ -1 +1 @@
-{"light-0.png": {"closebutton": [962, 737, 60, 43], "card_top": [810, 328, 32, 16], "tab_btn_disabled": [674, 312, 32, 32], "tab_btn_pressed": [742, 312, 32, 32], "globe": [884, 219, 72, 72], "btn_send_nfc": [996, 514, 18, 15], "shadow_right": [958, 220, 32, 5], "logo_atom_dull": [528, 346, 64, 64], "tab": [792, 346, 64, 64], "logo": [457, 163, 128, 128], "qrcode": [163, 146, 145, 145], "close": [906, 441, 88, 88], "btn_create_act_disabled": [953, 169, 32, 32], "create_act_text": [996, 490, 22, 10], "card_bottom": [962, 719, 32, 16], "confirmed": [896, 716, 64, 64], "carousel_deselected": [958, 227, 64, 64], "network": [499, 296, 48, 48], "blue_bg_round_rb": [906, 419, 31, 20], "action_bar": [602, 308, 36, 36], "pen": [660, 346, 64, 64], "arrow_back": [396, 294, 50, 50], "clock3": [698, 716, 64, 64], "contact": [448, 295, 49, 49], "star_big_inactive": [587, 163, 128, 128], "lightblue_bg_round_lb": [939, 419, 31, 20], "manualentry": [310, 157, 145, 134], "stepper_restore_password": [396, 412, 392, 117], "tab_disabled": [717, 169, 96, 32], "mail_icon": [924, 356, 65, 54], "tab_strip": [815, 169, 96, 32], "tab_btn": [708, 312, 32, 32], "btn_create_account": [943, 792, 64, 32], "btn_send_address": [996, 720, 18, 15], "add_contact": [549, 301, 51, 43], "gear": [2, 132, 159, 159], "wallets": [776, 312, 32, 32], "stepper_left": [2, 412, 392, 117], "nfc_stage_one": [2, 531, 489, 122], "nfc_clock": [698, 782, 243, 240], "btn_nfc": [1009, 812, 13, 12], "textinput_active": [790, 415, 114, 114], "clock2": [943, 826, 64, 64], "nfc_phone": [2, 655, 372, 367], "clock4": [764, 716, 64, 64], "paste_icon": [807, 214, 75, 77], "shadow": [726, 346, 64, 64], "carousel_selected": [943, 958, 64, 64], "card": [987, 169, 32, 32], "unconfirmed": [858, 346, 64, 64], "info": [462, 346, 64, 64], "electrum_icon640": [376, 702, 320, 320], "action_group_dark": [991, 362, 33, 48], "nfc": [594, 346, 64, 64], "clock1": [943, 892, 64, 64], "create_act_text_active": [996, 502, 22, 10], "icon_border": [396, 346, 64, 64], "stepper_full": [493, 536, 392, 117], "card_btn": [913, 169, 38, 32], "wallet": [376, 656, 49, 44], "important": [717, 203, 88, 88], "dialog": [1005, 419, 18, 20], "error": [887, 539, 128, 114], "stepper_restore_seed": [2, 293, 392, 117], "white_bg_round_top": [972, 419, 31, 20], "settings": [640, 312, 32, 32], "clock5": [830, 716, 64, 64]}}-
\ No newline at end of file
+{"light-0.png": {"closebutton": [641, 591, 60, 43], "card_top": [901, 792, 32, 16], "tab_btn_disabled": [833, 483, 32, 32], "tab_btn_pressed": [901, 483, 32, 32], "bit_logo": [589, 728, 44, 51], "globe": [686, 267, 72, 72], "btn_send_nfc": [955, 793, 18, 15], "shadow_right": [975, 803, 32, 5], "logo_atom_dull": [773, 517, 64, 64], "action_group_light": [431, 344, 33, 48], "tab": [390, 715, 64, 64], "logo": [296, 211, 128, 128], "qrcode": [2, 194, 145, 145], "close": [834, 810, 88, 88], "btn_create_act_disabled": [985, 911, 32, 32], "white_bg_round_top": [834, 788, 31, 20], "card_bottom": [867, 792, 32, 16], "confirmed": [839, 636, 64, 64], "overflow_btn_dn": [989, 520, 16, 10], "carousel_deselected": [760, 275, 64, 64], "network": [692, 467, 48, 48], "blue_bg_round_rb": [935, 495, 31, 20], "dropdown_background": [765, 599, 29, 35], "action_bar": [795, 479, 36, 36], "pen": [905, 517, 64, 64], "overflow_background": [796, 599, 29, 35], "arrow_back": [971, 650, 50, 50], "clock3": [641, 636, 64, 64], "contact": [641, 466, 49, 49], "star_big_inactive": [426, 211, 128, 128], "lightblue_bg_round_lb": [968, 495, 31, 20], "manualentry": [149, 205, 145, 134], "stepper_restore_password": [247, 464, 392, 117], "tab_disabled": [752, 233, 96, 32], "mail_icon": [522, 725, 65, 54], "tab_strip": [850, 233, 96, 32], "tab_btn": [867, 483, 32, 32], "btn_create_account": [948, 233, 64, 32], "btn_send_address": [935, 793, 18, 15], "add_contact": [742, 472, 51, 43], "gear": [2, 33, 105, 159], "wallets": [703, 594, 60, 40], "stepper_left": [247, 583, 392, 117], "nfc_stage_one": [324, 900, 489, 122], "nfc_clock": [2, 460, 243, 240], "btn_nfc": [752, 219, 13, 12], "textinput_active": [718, 784, 114, 114], "clock2": [958, 275, 64, 64], "nfc_phone": [556, 213, 128, 126], "clock4": [707, 636, 64, 64], "paste_icon": [945, 945, 75, 77], "shadow": [324, 715, 64, 64], "carousel_selected": [826, 275, 64, 64], "card": [686, 216, 64, 49], "unconfirmed": [456, 715, 64, 64], "info": [707, 517, 64, 64], "electrum_icon640": [2, 702, 320, 320], "action_button_group": [971, 520, 16, 10], "action_group_dark": [396, 344, 33, 48], "nfc": [839, 517, 64, 64], "contact_avatar": [971, 532, 49, 49], "clock1": [892, 275, 64, 64], "create_act_text_active": [971, 638, 22, 10], "icon_border": [641, 517, 64, 64], "stepper_full": [324, 781, 392, 117], "card_btn": [945, 911, 38, 32], "wallet": [635, 735, 49, 44], "important": [924, 810, 88, 88], "dialog": [1001, 495, 18, 20], "error": [815, 908, 128, 114], "stepper_restore_seed": [2, 341, 392, 117], "contact_overlay": [905, 636, 64, 64], "settings": [396, 394, 54, 64], "create_act_text": [995, 638, 22, 10], "clock5": [773, 636, 64, 64]}}+
\ No newline at end of file
diff --git a/gui/kivy/theming/light/action_bar.png b/gui/kivy/theming/light/action_bar.png
Binary files differ.
diff --git a/gui/kivy/theming/light/action_button_group.png b/gui/kivy/theming/light/action_button_group.png
Binary files differ.
diff --git a/gui/kivy/theming/light/action_group_light.png b/gui/kivy/theming/light/action_group_light.png
Binary files differ.
diff --git a/gui/kivy/theming/light/bit_logo.png b/gui/kivy/theming/light/bit_logo.png
Binary files differ.
diff --git a/gui/kivy/theming/light/card.png b/gui/kivy/theming/light/card.png
Binary files differ.
diff --git a/gui/kivy/theming/light/card_top.png b/gui/kivy/theming/light/card_top.png
Binary files differ.
diff --git a/gui/kivy/theming/light/contact.png b/gui/kivy/theming/light/contact.png
Binary files differ.
diff --git a/gui/kivy/theming/light/contact_avatar.png b/gui/kivy/theming/light/contact_avatar.png
Binary files differ.
diff --git a/gui/kivy/theming/light/contact_overlay.png b/gui/kivy/theming/light/contact_overlay.png
Binary files differ.
diff --git a/gui/kivy/theming/light/dropdown_background.png b/gui/kivy/theming/light/dropdown_background.png
Binary files differ.
diff --git a/gui/kivy/theming/light/gear.png b/gui/kivy/theming/light/gear.png
Binary files differ.
diff --git a/gui/kivy/theming/light/logo.png b/gui/kivy/theming/light/logo.png
Binary files differ.
diff --git a/gui/kivy/theming/light/manualentry.png b/gui/kivy/theming/light/manualentry.png
Binary files differ.
diff --git a/gui/kivy/theming/light/nfc_phone.png b/gui/kivy/theming/light/nfc_phone.png
Binary files differ.
diff --git a/gui/kivy/theming/light/overflow_background.png b/gui/kivy/theming/light/overflow_background.png
Binary files differ.
diff --git a/gui/kivy/theming/light/overflow_btn_dn.png b/gui/kivy/theming/light/overflow_btn_dn.png
Binary files differ.
diff --git a/gui/kivy/theming/light/qrcode.png b/gui/kivy/theming/light/qrcode.png
Binary files differ.
diff --git a/gui/kivy/theming/light/settings.png b/gui/kivy/theming/light/settings.png
Binary files differ.
diff --git a/gui/kivy/theming/light/tab_btn_pressed.png b/gui/kivy/theming/light/tab_btn_pressed.png
Binary files differ.
diff --git a/gui/kivy/theming/light/tab_strip.png b/gui/kivy/theming/light/tab_strip.png
Binary files differ.
diff --git a/gui/kivy/theming/light/wallets.png b/gui/kivy/theming/light/wallets.png
Binary files differ.
diff --git a/gui/kivy/tools/blacklist.txt b/gui/kivy/tools/blacklist.txt
@@ -0,0 +1,99 @@
+# eggs
+*.egg-info
+
+# unit test
+unittest/*
+
+# python config
+config/makesetup
+
+# unused pygame files
+pygame/_camera_*
+pygame/camera.pyo
+pygame/*.html
+pygame/*.bmp
+pygame/*.svg
+pygame/cdrom.so
+pygame/pygame_icon.icns
+pygame/LGPL
+pygame/threads/Py25Queue.pyo
+pygame/*.ttf
+pygame/mac*
+pygame/_numpy*
+pygame/sndarray.pyo
+pygame/surfarray.pyo
+pygame/_arraysurfarray.pyo
+
+# unused kivy files (platform specific)
+kivy/input/providers/wm_*
+kivy/input/providers/mactouch*
+kivy/input/providers/probesysfs*
+kivy/input/providers/mtdev*
+kivy/input/providers/hidinput*
+kivy/core/camera/camera_videocapture*
+kivy/core/spelling/*osx*
+kivy/core/video/video_pyglet*
+
+# unused encodings
+lib-dynload/*codec*
+encodings/cp*.pyo
+encodings/tis*
+encodings/shift*
+encodings/bz2*
+encodings/iso*
+encodings/undefined*
+encodings/johab*
+encodings/p*
+encodings/m*
+encodings/euc*
+encodings/k*
+encodings/unicode_internal*
+encodings/quo*
+encodings/gb*
+encodings/big5*
+encodings/hp*
+encodings/hz*
+
+# unused python modules
+bsddb/*
+wsgiref/*
+hotshot/*
+pydoc_data/*
+tty.pyo
+anydbm.pyo
+nturl2path.pyo
+LICENCE.txt
+macurl2path.pyo
+dummy_threading.pyo
+audiodev.pyo
+antigravity.pyo
+dumbdbm.pyo
+sndhdr.pyo
+__phello__.foo.pyo
+sunaudio.pyo
+os2emxpath.pyo
+multiprocessing/dummy*
+
+# unused binaries python modules
+lib-dynload/termios.so
+lib-dynload/_lsprof.so
+lib-dynload/*audioop.so
+#lib-dynload/mmap.so
+lib-dynload/_hotshot.so
+lib-dynload/_csv.so
+lib-dynload/future_builtins.so
+lib-dynload/_heapq.so
+lib-dynload/_json.so
+lib-dynload/grp.so
+lib-dynload/resource.so
+lib-dynload/pyexpat.so
+
+# odd files
+plat-linux3/regen
+
+#>sqlite3
+# conditionnal include depending if some recipes are included or not.
+sqlite3/*
+lib-dynload/_sqlite3.so
+#<sqlite3
+
diff --git a/gui/kivy/tools/buildozer.spec b/gui/kivy/tools/buildozer.spec
@@ -0,0 +1,172 @@
+[app]
+
+# (str) Title of your application
+title = Electrum
+
+# (str) Package name
+package.name = electrum
+
+# (str) Package domain (needed for android/ios packaging)
+package.domain = org.sierra3d
+
+# (str) Source code where the main.py live
+source.dir = .
+
+# (list) Source files to include (let empty to include all the files)
+source.include_exts = py,png,jpg,kv,atlas,ttf,*,txt, gif
+
+# (list) Source files to exclude (let empty to not exclude anything)
+source.exclude_exts = spec
+
+# (list) List of directory to exclude (let empty to not exclude anything)
+#source.exclude_dirs =
+
+# (list) List of exclusions using pattern matching
+#source.exclude_patterns = license,images/*/*.jpg
+
+# (str) Application versioning (method 1)
+#version.regex = __version__ = '(.*)'
+#version.filename = %(source.dir)s/main.py
+
+# (str) Application versioning (method 2)
+version = 1.9.7
+
+# (list) Application requirements
+requirements = pil, qrcode, ecdsa, pbkdf2, pyopenssl, plyer==master, kivy==master
+
+# (str) Presplash of the application
+presplash.filename = %(source.dir)s/gui/kivy/theming/splash.png
+
+# (str) Icon of the application
+icon.filename = %(source.dir)s/icons/electrum_android_launcher_icon.png
+
+# (str) Supported orientation (one of landscape, portrait or all)
+orientation = portrait
+
+# (bool) Indicate if the application should be fullscreen or not
+fullscreen = False
+
+
+#
+# Android specific
+#
+
+# (list) Permissions
+android.permissions = INTERNET, WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE , CAMERA, NFC
+# (int) Android API to use
+#android.api = 14
+
+# (int) Minimum API required (8 = Android 2.2 devices)
+#android.minapi = 8
+
+# (int) Android SDK version to use
+#android.sdk = 21
+
+# (str) Android NDK version to use
+#android.ndk = 9
+
+# (bool) Use --private data storage (True) or --dir public storage (False)
+android.private_storage = False
+
+# (str) Android NDK directory (if empty, it will be automatically downloaded.)
+#android.ndk_path =
+
+# (str) Android SDK directory (if empty, it will be automatically downloaded.)
+#android.sdk_path =
+
+# (str) Android entry point, default is ok for Kivy-based app
+#android.entrypoint = org.renpy.android.PythonActivity
+
+# (list) List of Java .jar files to add to the libs so that pyjnius can access
+# their classes. Don't add jars that you do not need, since extra jars can slow
+# down the build process. Allows wildcards matching, for example:
+# OUYA-ODK/libs/*.jar
+#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar
+android.add_jars = lib/android/zbar.jar
+
+# (list) List of Java files to add to the android project (can be java or a
+# directory containing the files)
+#android.add_src =
+
+# (str) python-for-android branch to use, if not master, useful to try
+# not yet merged features.
+android.branch = master
+
+# (str) OUYA Console category. Should be one of GAME or APP
+# If you leave this blank, OUYA support will not be enabled
+#android.ouya.category = GAME
+
+# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
+#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png
+
+# (str) XML file to include as an intent filters in <activity> tag
+#android.manifest.intent_filters =
+
+# (list) Android additionnal libraries to copy into libs/armeabi
+android.add_libs_armeabi = lib/android/*.so
+
+# (bool) Indicate whether the screen should stay on
+# Don't forget to add the WAKE_LOCK permission if you set this to True
+#android.wakelock = False
+
+# (list) Android application meta-data to set (key=value format)
+#android.meta_data =
+
+# (list) Android library project to add (will be added in the
+# project.properties automatically.)
+#android.library_references =
+
+#
+# iOS specific
+#
+
+# (str) Name of the certificate to use for signing the debug version
+# Get a list of available identities: buildozer ios list_identities
+#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"
+
+# (str) Name of the certificate to use for signing the release version
+#ios.codesign.release = %(ios.codesign.debug)s
+
+
+[buildozer]
+
+# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
+log_level = 2
+
+
+# -----------------------------------------------------------------------------
+# List as sections
+#
+# You can define all the "list" as [section:key].
+# Each line will be considered as a option to the list.
+# Let's take [app] / source.exclude_patterns.
+# Instead of doing:
+#
+# [app]
+# source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
+#
+# This can be translated into:
+#
+# [app:source.exclude_patterns]
+# license
+# data/audio/*.wav
+# data/images/original/*
+#
+
+# -----------------------------------------------------------------------------
+# Profiles
+#
+# You can extend section / key with a profile
+# For example, you want to deploy a demo version of your application without
+# HD content. You could first change the title to add "(demo)" in the name
+# and extend the excluded directories to remove the HD content.
+#
+# [app@demo]
+# title = My Application (demo)
+#
+# [app:source.exclude_patterns@demo]
+# images/hd/*
+#
+# Then, invoke the command line with the "demo" profile:
+#
+# buildozer --profile demo android debug
diff --git a/gui/kivy/ui_screens/mainscreen.kv b/gui/kivy/ui_screens/mainscreen.kv
@@ -1,286 +0,0 @@
-#:import TabbedCarousel electrum_gui.kivy.tabbed_carousel.TabbedCarousel
-#:import ScreenDashboard electrum_gui.kivy.screens.ScreenDashboard
-#:import Factory kivy.factory.Factory
-#:import Carousel electrum_gui.kivy.carousel.Carousel
-
-Screen:
- canvas.before:
- Color:
- rgba: 0.917, 0.917, 0.917, 1
- Rectangle:
- size: self.size
- pos: self.pos
- BoxLayout:
- orientation: 'vertical'
- ActionBar:
- id: action_bar
- size_hint: 1, None
- height: '40dp'
- border: 4, 4, 4, 4
- background_image: 'atlas://gui/kivy/theming/light/action_bar'
- ScreenManager:
- id: manager
- ScreenTabs:
- id: tabs
- name: "tabs"
- #ScreenPassword:
- # id: password
- # name: 'password'
-
-<TabbedCarousel>
- carousel: carousel
- do_default_tab: False
- Carousel:
- scroll_timeout: 190
- anim_type: 'out_quart'
- min_move: .05
- anim_move_duration: .1
- anim_cancel_duration: .54
- scroll_distance: '10dp'
- on_index: root.on_index(*args)
- id: carousel
-
-################################
-## Cards (under Dashboard)
-################################
-
-<Card@GridLayout>
- cols: 1
- padding: '12dp' , '22dp', '12dp' , '12dp'
- spacing: '12dp'
- size_hint: 1, None
- height: max(100, self.minimum_height)
- canvas.before:
- Color:
- rgba: 1, 1, 1, 1
- BorderImage:
- border: 9, 9, 9, 9
- source: 'atlas://gui/kivy/theming/light/card'
- size: self.size
- pos: self.pos
-
-<CardLabel@Label>
- color: 0.45, 0.45, 0.45, 1
- size_hint: 1, None
- text: ''
- text_size: self.width, None
- height: self.texture_size[1]
- halign: 'left'
- valign: 'top'
-
-<CardButton@Button>
- background_normal: 'atlas://gui/kivy/theming/light/card_btn'
- bold: True
- font_size: '10sp'
- color: 0.699, 0.699, 0.699, 1
- size_hint: None, None
- size: self.texture_size[0] + dp(32), self.texture_size[1] + dp(7)
-
-<CardSeparator@Widget>
- size_hint: 1, None
- height: dp(1)
- color: .909, .909, .909, 1
- canvas:
- Color:
- rgba: root.color if root.color else (0, 0, 0, 0)
- Rectangle:
- size: self.size
- pos: self.pos
-
-<CardRecentActivity@Card>
- BoxLayout:
- size_hint: 1, None
- height: lbl.height
- CardLabel:
- id: lbl
- text: _('RECENT ACTIVITY')
- CardButton:
- id: btn_see_all
- text: _('SEE ALL')
- font_size: '12sp'
- on_release: app.gui.main_gui.update_history(see_all=True)
- GridLayout:
- id: content
- spacing: '7dp'
- cols: 1
- size_hint: 1, None
- height: self.minimum_height
- CardSeparator
-
-<CardPaymentRequest@Card>
- CardLabel:
- text: _('PAYMENT REQUEST')
- CardSeparator:
-
-<CardStatusInfo@Card>
- status: app.status
- base_unit: 'BTC'
- quote_text: '.'
- unconfirmed: '.'
- BoxLayout:
- size_hint: 1, None
- height: '72dp'
- IconButton:
- mipmap: True
- color: .90, .90, .90, 1
- source: 'atlas://gui/kivy/theming/light/qrcode'
- size_hint: None, 1
- width: self.height
- on_release:
- Factory.WalletAddressesDialog().open()
- GridLayout:
- id: grid
- cols: 1
- orientation: 'vertical'
- CardLabel:
- halign: 'right'
- valign: 'top'
- bold: True
- size_hint: 1, None
- font_size: '38sp'
- text:
- '[color=#4E4F4F]{}[/color]'\
- '[sup][color=9b948d]{}[/color][/sup]'\
- .format(unicode(root.status), root.base_unit)
- CardLabel
- halign: 'right'
- markup: True
- font_size: '15dp'
- text: '[color=#c3c3c3]{}[/color]'.format(root.quote_text)
- CardLabel
- halign: 'right'
- markup: True
- text: '[color=#c3c3c3]{}[/color]'.format(root.unconfirmed)
-
-<DashboardActionView@ActionView>
- ActionPrevious:
- id: action_previous
- app_icon: 'atlas://gui/kivy/theming/light/wallets'
- with_previous: False
- size_hint: None, 1
- mipmap: True
- width: '77dp'
- ActionButton:
- id: action_logo
- important: True
- size_hint: 1, 1
- markup: True
- mipmap: True
- bold: True
- font_size: '22dp'
- icon: 'atlas://gui/kivy/theming/light/logo'
- minimum_width: '1dp'
- ActionButton:
- id: action_contact
- important: True
- width: '25dp'
- icon: 'atlas://gui/kivy/theming/light/add_contact'
- text: 'Add Contact'
- on_release: NewContactDialog().open()
- ActionOverflow:
- id: action_preferences
- canvas.after:
- Color:
- rgba: 1, 1, 1, 1
- border: 0, 0, 0, 0
- overflow_image: 'atlas://gui/kivy/theming/light/settings'
- width: '32dp'
- ActionButton:
- text: _('Seed')
- on_release:
- action_preferences._dropdown.dismiss()
- if app.wallet.seed: app.gui.main_gui.protected_seed_dialog(self)
- ActionButton:
- text: _('Password')
- ActionButton:
- text: _('Network')
- on_release:
- app.root.current = 'screen_network'
- action_preferences._dropdown.dismiss()
- ActionButton:
- text: _('Preferences')
- on_release:
- action_preferences._dropdown.dismiss()
- app.gui.main_gui.show_settings_dialog(self)
-
-<ScreenDashboard>
- action_view: Factory.DashboardActionView()
- ScrollView:
- do_scroll_x: False
- RelativeLayout:
- size_hint: 1, None
- height: grid.height
- GridLayout
- id: grid
- cols: 1 #if root.width < root.height else 2
- size_hint: 1, None
- height: self.minimum_height
- padding: '12dp'
- spacing: '12dp'
- GridLayout:
- cols: 1
- size_hint: 1, None
- height: self.minimum_height
- spacing: '12dp'
- orientation: 'vertical'
- CardStatusInfo:
- id: status_card
- CardPaymentRequest:
- id: payment_card
- CardRecentActivity:
- id: recent_activity_card
-
-<CleanHeader@TabbedPanelHeader>
- border: 0, 0, 4, 0
- markup: False
- color: (0.191, 0.558, 0.742, 1) if self.state == 'down' else (0.636, 0.636, 0.636, 1)
- text_size: self.size
- halign: 'center'
- valign: 'middle'
- bold: True
- font_size: '12sp'
- background_normal: 'atlas://gui/kivy/theming/light/tab_btn'
- background_disabled_normal: 'atlas://gui/kivy/theming/light/tab_btn_disabled'
- background_down: 'atlas://gui/kivy/theming/light/tab_btn_pressed'
- canvas.before:
- Color:
- rgba: 1, 1, 1, .7
- Rectangle:
- size: self.size
- pos: self.x + 1, self.y - 1
- texture: self.texture
-
-<ScreenTabs@Screen>
- TabbedCarousel:
- id: panel
- background_image: 'atlas://gui/kivy/theming/light/tab'
- strip_image: 'atlas://gui/kivy/theming/light/tab_strip'
- strip_border: 4, 0, 2, 0
- ScreenDashboard:
- id: screen_dashboard
- tab: tab_dashboard
- #ScreenSend:
- # id: screen_send
- # tab: tab_send
- #ScreenReceive:
- # id: screen_receive
- # tab: tab_receive
- #ScreenContacts:
- # id: screen_contacts
- # tab: tab_contacts
- CleanHeader:
- id: tab_dashboard
- text: _('DASHBOARD')
- slide: 0
- #CleanHeader:
- # id: tab_send
- # text: _('SEND')
- # slide: 1
- #CleanHeader:
- # id: tab_receive
- # text: _('RECEIVE')
- # slide: 2
- #CleanHeader:
- # id: tab_contacts
- # text: _('CONTACTS')
- # slide: 3-
\ No newline at end of file
diff --git a/gui/kivy/ui_screens/screenreceive.kv b/gui/kivy/ui_screens/screenreceive.kv
@@ -0,0 +1,129 @@
+<ScreenReceiveContent@BoxLayout>
+ opacity: 0
+ padding: '12dp', '12dp', '12dp', '12dp'
+ spacing: '12dp'
+ mode: 'qr'
+ orientation: 'vertical'
+ SendReceiveToggle
+ SendToggle:
+ id: toggle_qr
+ text: 'QR'
+ state: 'down' if root.mode == 'qr' else 'normal'
+ source: 'atlas://gui/kivy/theming/light/qrcode'
+ background_down: 'atlas://gui/kivy/theming/light/btn_send_address'
+ on_release:
+ if root.mode == 'qr': root.mode = 'nr'
+ root.mode = 'qr'
+ SendToggle:
+ id: toggle_nfc
+ text: 'NFC'
+ state: 'down' if root.mode == 'nfc' else 'normal'
+ source: 'atlas://gui/kivy/theming/light/nfc'
+ background_down: 'atlas://gui/kivy/theming/light/btn_send_nfc'
+ on_release:
+ if root.mode == 'nfc': root.mode = 'nr'
+ root.mode = 'nfc'
+ GridLayout:
+ id: grid
+ cols: 1
+ #size_hint: 1, None
+ #height: self.minimum_height
+ SendReceiveCardTop
+ height: '110dp'
+ BoxLayout:
+ size_hint: 1, None
+ height: '42dp'
+ rows: 1
+ Label:
+ color: amount_e.foreground_color
+ bold: True
+ text_size: self.size
+ valign: 'bottom'
+ font_size: '22sp'
+ text: app.base_unit
+ size_hint_x: .25
+ ELTextInput:
+ id: amount_e
+ input_type: 'number'
+ multiline: False
+ bold: True
+ font_size: '50sp'
+ foreground_color: .308, .308, .308, 1
+ background_normal: 'atlas://gui/kivy/theming/light/tab_btn'
+ pos_hint: {'top': 1.5}
+ size_hint: .7, None
+ height: '67dp'
+ hint_text: 'Amount'
+ text: '0.0'
+ on_text_validate: payto_e.focus = True
+ CardSeparator
+ BoxLayout:
+ size_hint: 1, None
+ height: '32dp'
+ spacing: '5dp'
+ Label:
+ id: lbl_quote
+ font_size: '12dp'
+ size_hint: .5, 1
+ color: .761, .761, .761, 1
+ #text: app.create_quote_text(Decimal(amount_e.text))
+ text_size: self.size
+ halign: 'left'
+ valign: 'middle'
+ Label:
+ color: lbl_quote.color
+ font_size: '12dp'
+ text: 'Ask to scan the QR below'
+ text_size: self.size
+ halign: 'right'
+ valign: 'middle'
+ SendReceiveBlueBottom
+ id: blue_bottom
+ padding: '12dp', 0, '12dp', '12dp'
+ WalletSelector:
+ id: wallet_selection
+ foreground_color: blue_bottom.foreground_color
+ opacity: 1 if app.expert_mode else 0
+ size_hint: 1, None
+ height: blue_bottom.item_height if app.expert_mode else 0
+ CardSeparator
+ opacity: wallet_selection.opacity
+ color: blue_bottom.foreground_color
+ AddressSelector:
+ id: address_selection
+ foreground_color: blue_bottom.foreground_color
+ opacity: 1 if app.expert_mode else 0
+ size_hint: 1, None
+ height: blue_bottom.item_height if app.expert_mode else 0
+ on_text:
+ if not args[1].startswith('Select'):\
+ qr.data = app.encode_uri(self.text)
+ CardSeparator
+ opacity: address_selection.opacity
+ color: blue_bottom.foreground_color
+ Widget:
+ size_hint_y: None
+ height: dp(10)
+ BoxLayout
+ #size_hint: 1, None
+ #height: '160dp' if app.expert_mode else '220dp'
+ Widget
+ QRCodeWidget:
+ id: qr
+ size_hint: None, 1
+ width: self.height
+ data: app.encode_uri(app.wallet.addresses()[0]) if app.wallet.addresses() else ''
+ on_touch_down:
+ if self.collide_point(*args[1].pos):\
+ app.show_info_bubble(icon=self.ids.qrimage.texture, text='texture')
+ Widget
+ CreateAccountButtonGreen:
+ background_color: (1, 1, 1, 1) if self.disabled else ((.258, .80, .388, 1) if self.state == 'normal' else (.203, .490, .741, 1))
+ text: _('Goto next step') if app.wallet.seed else _('Create unsigned transaction')
+ size_hint_y: None
+ height: '38dp'
+ disabled: True if wallet_selection.opacity == 0 else False
+ on_release:
+ message = 'sending {} {} to {}'.format(\
+ app.base_unit, amount_e.text, payto_e.text)
+ app.gui.main_gui.do_send(self, message=message)+
\ No newline at end of file
diff --git a/gui/kivy/ui_screens/screensend.kv b/gui/kivy/ui_screens/screensend.kv
@@ -0,0 +1,187 @@
+<TextInputSendBlue@TextInput>
+ padding: '5dp'
+ size_hint: 1, None
+ height: '27dp'
+ pos_hint: {'center_y':.5}
+ multiline: False
+ hint_text_color: self.foreground_color
+ foreground_color: .843, .914, .972, 1
+ background_color: 1, 1, 1, 1
+ background_normal: 'atlas://gui/kivy/theming/light/tab_btn'
+ background_active: 'atlas://gui/kivy/theming/light/textinput_active'
+
+<ScreenSendContent@BoxLayout>
+ opacity: 0
+ padding: '12dp', '12dp', '12dp', '12dp'
+ spacing: '12dp'
+ orientation: 'vertical'
+ mode: 'address'
+ SendReceiveToggle:
+ SendToggle:
+ id: toggle_address
+ text: 'ADDRESS'
+ group: 'send_type'
+ state: 'down' if root.mode == 'address' else 'normal'
+ source: 'atlas://gui/kivy/theming/light/globe'
+ background_down: 'atlas://gui/kivy/theming/light/btn_send_address'
+ on_release:
+ if root.mode == 'address': root.mode = 'fc'
+ root.mode = 'address'
+ SendToggle:
+ id: toggle_nfc
+ text: 'NFC'
+ group: 'send_type'
+ state: 'down' if root.mode == 'nfc' else 'normal'
+ source: 'atlas://gui/kivy/theming/light/nfc'
+ background_down: 'atlas://gui/kivy/theming/light/btn_send_nfc'
+ on_release:
+ if root.mode == 'nfc': root.mode = 'str'
+ root.mode = 'nfc'
+ GridLayout:
+ id: grid
+ cols: 1
+ size_hint: 1, None
+ height: self.minimum_height
+ SendReceiveCardTop
+ id: card_address
+ BoxLayout
+ size_hint: 1, None
+ height: '42dp'
+ rows: 1
+ Label
+ bold: True
+ color: amount_e.foreground_color
+ text_size: self.size
+ valign: 'bottom'
+ font_size: '22sp'
+ text: app.base_unit
+ size_hint_x: .25
+ ELTextInput:
+ id: amount_e
+ input_type: 'number'
+ multiline: False
+ bold: True
+ font_size: '50sp'
+ foreground_color: .308, .308, .308, 1
+ background_normal: 'atlas://gui/kivy/theming/light/tab_btn'
+ pos_hint: {'top': 1.5}
+ size_hint: .7, None
+ height: '67dp'
+ hint_text: 'Amount'
+ text: '0.0'
+ on_text_validate: payto_e.focus = True
+ CardSeparator
+ BoxLayout:
+ size_hint: 1, None
+ height: '42dp'
+ spacing: '5dp'
+ Label:
+ font_size: '12dp'
+ color: lbl_fee.color
+ text: app.gui.main_gui.create_quote_text(Decimal(amount_e.text)) if hasattr(app, 'gui') else '0'
+ text_size: self.size
+ halign: 'left'
+ valign: 'middle'
+ Label:
+ id: lbl_fee
+ color: .761, .761, .761, 1
+ font_size: '12dp'
+ text: '[b]{}[/b] of fee'.format(fee_e.value)
+ text_size: self.size
+ halign: 'right'
+ valign: 'middle'
+ IconButton:
+ id: fee_e
+ source: 'atlas://gui/kivy/theming/light/contact'
+ text: str(self.value)
+ value: .0005
+ pos_hint: {'center_y': .5}
+ size_hint: None, None
+ size: '32dp', '32dp'
+ on_release: print 'TODO'
+ SendReceiveBlueBottom:
+ id: blue_bottom
+ size_hint: 1, None
+ height: self.minimum_height
+ BoxLayout
+ size_hint: 1, None
+ height: blue_bottom.item_height
+ spacing: '5dp'
+ Image:
+ source: 'atlas://gui/kivy/theming/light/contact'
+ size_hint: None, None
+ size: '22dp', '22dp'
+ pos_hint: {'center_y': .5}
+ TextInputSendBlue:
+ id: payto_e
+ hint_text: "Enter Contact or adress"
+ on_text_validate:
+ Factory.Animation(opacity=1,\
+ height=blue_bottom.item_height)\
+ .start(message_selection)
+ message_e.focus = True
+ Widget:
+ size_hint: None, None
+ width: dp(2)
+ height: qr.height
+ pos_hint: {'center_y':.5}
+ canvas.after:
+ Rectangle:
+ size: self.size
+ pos: self.pos
+ IconButton:
+ id: qr
+ source: 'atlas://gui/kivy/theming/light/qrcode'
+ pos_hint: {'center_y': .5}
+ size_hint: None, None
+ size: '22dp', '22dp'
+ CardSeparator
+ opacity: message_selection.opacity
+ color: blue_bottom.foreground_color
+ BoxLayout:
+ id: message_selection
+ opacity: 1 if app.expert_mode else 0
+ size_hint: 1, None
+ height: blue_bottom.item_height if app.expert_mode else 0
+ spacing: '5dp'
+ Image:
+ source: 'atlas://gui/kivy/theming/light/pen'
+ size_hint: None, None
+ size: '22dp', '22dp'
+ pos_hint: {'center_y': .5}
+ TextInputSendBlue:
+ id: message_e
+ hint_text: 'Enter description here'
+ on_text_validate:
+ anim = Factory.Animation(opacity=1, height=blue_bottom.item_height)
+ anim.start(wallet_selection)
+ #anim.start(address_selection)
+ CardSeparator
+ opacity: wallet_selection.opacity
+ color: blue_bottom.foreground_color
+ WalletSelector:
+ id: wallet_selection
+ foreground_color: blue_bottom.foreground_color
+ opacity: 1 if app.expert_mode else 0
+ size_hint: 1, None
+ height: blue_bottom.item_height if app.expert_mode else 0
+ CardSeparator
+ opacity: address_selection.opacity
+ color: blue_bottom.foreground_color
+ AddressSelector:
+ id: address_selection
+ foreground_color: blue_bottom.foreground_color
+ opacity: 1 if app.expert_mode else 0
+ size_hint: 1, None
+ height: blue_bottom.item_height if app.expert_mode else 0
+ CreateAccountButtonGreen:
+ background_color: (1, 1, 1, 1) if self.disabled else ((.258, .80, .388, 1) if self.state == 'normal' else (.203, .490, .741, 1))
+ text: _('Goto next step') if app.wallet.seed else _('Create unsigned transaction')
+ size_hint_y: None
+ height: '38dp'
+ disabled: True if wallet_selection.opacity == 0 else False
+ on_release:
+ message = 'sending {} {} to {}'.format(\
+ app.base_unit, amount_e.text, payto_e.text)
+ app.gui.main_gui.do_send(self, message=message)
+ Widget
diff --git a/gui/kivy/uix/__init__.py b/gui/kivy/uix/__init__.py
@@ -0,0 +1 @@
+
diff --git a/gui/kivy/combobox.py b/gui/kivy/uix/combobox.py
diff --git a/gui/kivy/console.py b/gui/kivy/uix/console.py
diff --git a/gui/kivy/uix/dialogs/__init__.py b/gui/kivy/uix/dialogs/__init__.py
@@ -0,0 +1,190 @@
+from kivy.app import App
+from kivy.clock import Clock
+from kivy.factory import Factory
+from kivy.properties import NumericProperty, StringProperty, BooleanProperty
+from kivy.core.window import Window
+
+from electrum.i18n import _
+
+
+
+class AnimatedPopup(Factory.Popup):
+ ''' An Animated Popup that animates in and out.
+ '''
+
+ anim_duration = NumericProperty(.25)
+ '''Duration of animation to be used
+ '''
+
+ __events__ = ['on_activate', 'on_deactivate']
+
+
+ def on_activate(self):
+ '''Base function to be overridden on inherited classes.
+ Called when the popup is done animating.
+ '''
+ pass
+
+ def on_deactivate(self):
+ '''Base function to be overridden on inherited classes.
+ Called when the popup is done animating.
+ '''
+ pass
+
+ def open(self):
+ '''Do the initialization of incoming animation here.
+ Override to set your custom animation.
+ '''
+ def on_complete(*l):
+ self.dispatch('on_activate')
+
+ self.opacity = 0
+ super(AnimatedPopup, self).open()
+ anim = Factory.Animation(opacity=1, d=self.anim_duration)
+ anim.bind(on_complete=on_complete)
+ anim.start(self)
+
+ def dismiss(self):
+ '''Do the initialization of incoming animation here.
+ Override to set your custom animation.
+ '''
+ def on_complete(*l):
+ super(AnimatedPopup, self).dismiss()
+ self.dispatch('on_deactivate')
+
+ anim = Factory.Animation(opacity=0, d=.25)
+ anim.bind(on_complete=on_complete)
+ anim.start(self)
+
+class EventsDialog(AnimatedPopup):
+ ''' Abstract Popup that provides the following events
+ .. events::
+ `on_release`
+ `on_press`
+ '''
+
+ __events__ = ('on_release', 'on_press')
+
+ def __init__(self, **kwargs):
+ super(EventsDialog, self).__init__(**kwargs)
+ self._on_release = kwargs.get('on_release')
+
+ def on_release(self, instance):
+ pass
+
+ def on_press(self, instance):
+ pass
+
+ def close(self):
+ self._on_release = None
+ self.dismiss()
+
+
+class SelectionDialog(EventsDialog):
+
+ def add_widget(self, widget, index=0):
+ if self.content:
+ self.content.add_widget(widget, index)
+ return
+ super(SelectionDialog, self).add_widget(widget)
+
+
+class InfoBubble(Factory.Bubble):
+ '''Bubble to be used to display short Help Information'''
+
+ message = StringProperty(_('Nothing set !'))
+ '''Message to be displayed; defaults to "nothing set"'''
+
+ icon = StringProperty('')
+ ''' Icon to be displayed along with the message defaults to ''
+
+ :attr:`icon` is a `StringProperty` defaults to `''`
+ '''
+
+ fs = BooleanProperty(False)
+ ''' Show Bubble in half screen mode
+
+ :attr:`fs` is a `BooleanProperty` defaults to `False`
+ '''
+
+ modal = BooleanProperty(False)
+ ''' Allow bubble to be hidden on touch.
+
+ :attr:`modal` is a `BooleanProperty` defauult to `False`.
+ '''
+
+ exit = BooleanProperty(False)
+ '''Indicates whether to exit app after bubble is closed.
+
+ :attr:`exit` is a `BooleanProperty` defaults to False.
+ '''
+
+ dim_background = BooleanProperty(False)
+ ''' Indicates Whether to draw a background on the windows behind the bubble.
+
+ :attr:`dim` is a `BooleanProperty` defaults to `False`.
+ '''
+
+ def on_touch_down(self, touch):
+ if self.modal:
+ return True
+ self.hide()
+ if self.collide_point(*touch.pos):
+ return True
+
+ def show(self, pos, duration, width=None, modal=False, exit=False):
+ '''Animate the bubble into position'''
+ self.modal, self.exit = modal, exit
+ if width:
+ self.width = width
+ if self.modal:
+ from kivy.uix.modalview import ModalView
+ self._modal_view = m = ModalView(background_color=[.5, .5, .5, .2])
+ Window.add_widget(m)
+ m.add_widget(self)
+ else:
+ Window.add_widget(self)
+ # wait for the bubble to adjust it's size according to text then animate
+ Clock.schedule_once(lambda dt: self._show(pos, duration))
+
+ def _show(self, pos, duration):
+
+ def on_stop(*l):
+ if duration:
+ Clock.schedule_once(self.hide, duration + .5)
+
+ self.opacity = 0
+ arrow_pos = self.arrow_pos
+ if arrow_pos[0] in ('l', 'r'):
+ pos = pos[0], pos[1] - (self.height/2)
+ else:
+ pos = pos[0] - (self.width/2), pos[1]
+
+ self.limit_to = Window
+
+ anim = Factory.Animation(opacity=1, pos=pos, d=.32)
+ anim.bind(on_complete=on_stop)
+ anim.cancel_all(self)
+ anim.start(self)
+
+
+ def hide(self, now=False):
+ ''' Auto fade out the Bubble
+ '''
+ def on_stop(*l):
+ if self.modal:
+ m = self._modal_view
+ m.remove_widget(self)
+ Window.remove_widget(m)
+ Window.remove_widget(self)
+ if self.exit:
+ App.get_running_app().stop()
+ import sys
+ sys.exit()
+ if now:
+ return on_stop()
+
+ anim = Factory.Animation(opacity=0, d=.25)
+ anim.bind(on_complete=on_stop)
+ anim.cancel_all(self)
+ anim.start(self)
diff --git a/gui/kivy/uix/dialogs/carousel_dialog.py b/gui/kivy/uix/dialogs/carousel_dialog.py
@@ -0,0 +1,239 @@
+''' Dialogs intended to be used along with a slidable carousel inside
+and indicators on either top, left, bottom or right side. These indicators can
+be touched to travel to a particular slide.
+'''
+from electrum.i18n import _
+
+
+from kivy.app import App
+from kivy.clock import Clock
+from kivy.properties import NumericProperty, ObjectProperty
+from kivy.factory import Factory
+from kivy.lang import Builder
+
+import weakref
+
+
+class CarouselHeader(Factory.TabbedPanelHeader):
+ '''Tabbed Panel Header with a circular image on top to be used as a
+ indicator for the current slide.
+ '''
+
+ slide = NumericProperty(0)
+ ''' indicates the link to carousels slide'''
+
+
+class CarouselDialog(Factory.AnimatedPopup):
+ ''' A Popup dialog with a CarouselIndicator used as the content.
+ '''
+
+ carousel_content = ObjectProperty(None)
+
+ def add_widget(self, widget, index=0):
+ if isinstance(widget, Factory.Carousel):
+ super(CarouselDialog, self).add_widget(widget, index)
+ return
+ if 'carousel_content' not in self.ids.keys():
+ super(CarouselDialog, self).add_widget(widget)
+ return
+ self.carousel_content.add_widget(widget, index)
+
+
+class WalletAddressesDialog(CarouselDialog):
+ ''' Show current wallets and their addresses using qrcode widget
+ '''
+
+ def __init__(self, **kwargs):
+ self._loaded = False
+ super(WalletAddressesDialog, self).__init__(**kwargs)
+
+ def on_activate(self):
+ # do activate routine here
+ slide = None
+
+ if not self._loaded:
+ self._loaded = True
+ CarouselHeader = Factory.CarouselHeader
+ ch = CarouselHeader()
+ ch.slide = 0 # idx
+ slide = Factory.ScreenAddress()
+
+ slide.tab = ch
+
+ self.add_widget(slide)
+ self.add_widget(ch)
+
+ app = App.get_running_app()
+ if not slide:
+ slide = self.carousel_content.carousel.slides[0]
+
+ # add a tab for each wallet
+ self.wallet_name = app.wallet.get_account_names()[0]
+ labels = app.wallet.labels
+
+ addresses = app.wallet.addresses()
+ _labels = {}
+
+ for address in addresses:
+ _labels[labels.get(address, address)] = address
+
+ slide.labels = _labels
+ Clock.schedule_once(lambda dt: self._setup_slide(slide))
+
+ def _setup_slide(self, slide):
+ btn_address = slide.ids.btn_address
+ btn_address.values = values = slide.labels.keys()
+ if not btn_address.text:
+ btn_address.text = values[0]
+
+
+class RecentActivityDialog(CarouselDialog):
+ '''
+ '''
+ def on_activate(self):
+ # animate to first slide
+ carousel = self.carousel_content.carousel
+ carousel.load_slide(carousel.slides[0])
+
+ item = self.item
+ try:
+ self.address = item.address
+ except ReferenceError:
+ self.dismiss()
+ return
+
+ self.amount = item.amount[1:]
+ self.amount_color = item.amount_color
+ self.confirmations = item.confirmations
+ self.quote_text = item.quote_text
+ date_time = item.date.split()
+ if len(date_time) == 2:
+ self.date = date_time[0]
+ self.time = date_time[1]
+ self.status = 'Validated'
+ else:
+ self.date = item.date
+ self.status = 'Pending'
+ self.tx_hash = item.tx_hash
+
+ app = App.get_running_app()
+
+ tx_hash = item.tx_hash
+ tx = app.wallet.transactions.get(tx_hash)
+
+ if tx_hash in app.wallet.transactions.keys():
+ is_relevant, is_mine, v, fee = app.wallet.get_tx_value(tx)
+ conf, timestamp = app.wallet.verifier.get_confirmations(tx_hash)
+ else:
+ is_mine = False
+
+ self.is_mine = is_mine
+
+ if is_mine:
+ if fee is not None:
+ self.fee = app.format_amount(fee)
+ else:
+ self.fee = 'unknown'
+
+ labels = app.wallet.labels
+ addresses = app.wallet.addresses()
+ _labels = {}
+
+ self.wallet_name = app.wallet.get_account_names()[0]
+ for address in addresses:
+ _labels[labels.get(address, address)] = address
+
+ self.labels = _labels
+
+ def open(self):
+ self._trans_actv = self._det_actv = self._in_actv\
+ = self._out_actv = False
+ super(RecentActivityDialog, self).open()
+
+ def dismiss(self):
+ if self._in_actv:
+ self.ids.list_inputs.content = ""
+ self.ids.list_inputs.clear_widgets()
+ if self._out_actv:
+ self.ids.list_outputs.content = ""
+ self.ids.list_outputs.clear_widgets()
+ super(RecentActivityDialog, self).dismiss()
+
+ def dropdown_selected(self, value):
+ app = App.get_running_app()
+ try:
+ labels = self.labels
+ except AttributeError:
+ return
+
+ address = labels.get(self.address, self.address[1:])
+
+ if value.startswith(_('Copy')):
+ app.copy(address)
+ elif value.startswith(_('Send')):
+ app.send_payment(address)
+ self.dismiss()
+
+ def activate_screen_transactionid(self, screen):
+ if self._trans_actv:
+ return
+
+ self._trans_actv = True
+ Clock.schedule_once(
+ lambda dt: self._activate_screen_transactionid(screen), .1)
+
+ def _activate_screen_transactionid(self, screen):
+ content = screen.content
+ if not content:
+ content = Factory.RecentActivityScrTransID()
+ screen.content = content
+ screen.add_widget(content)
+ content.tx_hash = self.tx_hash
+ content.text_color = self.text_color
+ content.carousel_content = self.carousel_content
+
+ def activate_screen_inputs(self, screen):
+ if self._in_actv:
+ return
+
+ self._in_actv = True
+ Clock.schedule_once(
+ lambda dt: self._activate_screen_inputs(screen), .1)
+
+ def _activate_screen_inputs(self, screen):
+ content = screen.content
+ if not content:
+ content = Factory.RecentActivityScrInputs()
+ screen.content = content
+ screen.add_widget(content)
+ self.populate_inputs_outputs(content, 'in')
+
+ def activate_screen_outputs(self, screen):
+ if self._out_actv:
+ return
+
+ self._out_actv = True
+ Clock.schedule_once(
+ lambda dt: self._activate_screen_outputs(screen), .1)
+
+ def _activate_screen_outputs(self, screen):
+ content = screen.content
+ if not content:
+ content = Factory.RecentActivityScrOutputs()
+ screen.content = content
+ screen.add_widget(content)
+ self.populate_inputs_outputs(content, 'out')
+
+ def populate_inputs_outputs(self, content, mode):
+ app = App.get_running_app()
+ tx_hash = self.tx_hash
+ if tx_hash:
+ tx = app.wallet.transactions.get(tx_hash)
+ if mode == 'out':
+ content.data = \
+ [(address, app.format_amount(value))\
+ for address, value in tx.outputs]
+ else:
+ content.data = \
+ [(input['address'], input['prevout_hash'])\
+ for input in tx.inputs]
diff --git a/gui/kivy/uix/dialogs/create_restore.py b/gui/kivy/uix/dialogs/create_restore.py
@@ -0,0 +1,488 @@
+''' Dialogs and widgets Responsible for creation, restoration of accounts are
+defined here.
+
+Namely: CreateAccountDialog, CreateRestoreDialog, ChangePasswordDialog,
+RestoreSeedDialog
+'''
+
+from functools import partial
+
+from kivy.app import App
+from kivy.clock import Clock
+from kivy.lang import Builder
+from kivy.properties import ObjectProperty, StringProperty, OptionProperty
+from kivy.core.window import Window
+
+from electrum_gui.kivy.uix.dialogs import EventsDialog
+
+from electrum.i18n import _
+
+
+Builder.load_string('''
+#:import Window kivy.core.window.Window
+#:import _ electrum.i18n._
+
+
+<CreateAccountTextInput@TextInput>
+ border: 4, 4, 4, 4
+ font_size: '15sp'
+ padding: '15dp', '15dp'
+ background_color: (1, 1, 1, 1) if self.focus else (0.454, 0.698, 0.909, 1)
+ foreground_color: (0.31, 0.31, 0.31, 1) if self.focus else (0.835, 0.909, 0.972, 1)
+ hint_text_color: self.foreground_color
+ background_active: 'atlas://gui/kivy/theming/light/create_act_text_active'
+ background_normal: 'atlas://gui/kivy/theming/light/create_act_text_active'
+ size_hint_y: None
+ height: '48sp'
+
+
+<-CreateAccountDialog>
+ text_color: .854, .925, .984, 1
+ auto_dismiss: False
+ size_hint: None, None
+ canvas.before:
+ Color:
+ rgba: 0, 0, 0, .9
+ Rectangle:
+ size: Window.size
+ Color:
+ rgba: .239, .588, .882, 1
+ Rectangle:
+ size: Window.size
+
+ crcontent: crcontent
+ # add electrum icon
+ FloatLayout:
+ size_hint: None, None
+ size: 0, 0
+ IconButton:
+ id: but_close
+ size_hint: None, None
+ size: '27dp', '27dp'
+ top: Window.height - dp(10)
+ right: Window.width - dp(10)
+ source: 'atlas://gui/kivy/theming/light/closebutton'
+ on_release: root.dispatch('on_press', self)
+ on_release: root.dispatch('on_release', self)
+ BoxLayout:
+ orientation: 'vertical' if self.width < self.height else 'horizontal'
+ padding:
+ min(dp(42), self.width/8), min(dp(60), self.height/9.7),\
+ min(dp(42), self.width/8), min(dp(72), self.height/8)
+ spacing: '27dp'
+ GridLayout:
+ id: grid_logo
+ cols: 1
+ pos_hint: {'center_y': .5}
+ size_hint: 1, .62
+ #height: self.minimum_height
+ Image:
+ id: logo_img
+ mipmap: True
+ allow_stretch: True
+ size_hint: 1, None
+ height: '110dp'
+ source: 'atlas://gui/kivy/theming/light/electrum_icon640'
+ Widget:
+ size_hint: 1, None
+ height: 0 if stepper.opacity else dp(15)
+ Label:
+ color: root.text_color
+ opacity: 0 if stepper.opacity else 1
+ text: 'ELECTRUM'
+ size_hint: 1, None
+ height: self.texture_size[1] if self.opacity else 0
+ font_size: '33sp'
+ font_name: 'data/fonts/tron/Tr2n.ttf'
+ Image:
+ id: stepper
+ allow_stretch: True
+ opacity: 0
+ source: 'atlas://gui/kivy/theming/light/stepper_left'
+ size_hint: 1, None
+ height: grid_logo.height/2.5 if self.opacity else 0
+ Widget:
+ size_hint: None, None
+ size: '5dp', '5dp'
+ GridLayout:
+ cols: 1
+ id: crcontent
+ spacing: '13dp'
+
+
+<CreateRestoreDialog>
+ Label:
+ color: root.text_color
+ size_hint: 1, None
+ text_size: self.width, None
+ height: self.texture_size[1]
+ text:
+ _("Wallet file not found!!")+"\\n\\n" +\
+ _("Do you want to create a new wallet ")+\
+ _("or restore an existing one?")
+ Widget
+ size_hint: 1, None
+ height: dp(15)
+ GridLayout:
+ id: grid
+ orientation: 'vertical'
+ cols: 1
+ spacing: '14dp'
+ size_hint: 1, None
+ height: self.minimum_height
+ CreateAccountButtonGreen:
+ id: create
+ text: _('Create a Wallet')
+ root: root
+ CreateAccountButtonBlue:
+ id: restore
+ text: _('I already have a wallet')
+ root: root
+
+
+<RestoreSeedDialog>
+ GridLayout
+ cols: 1
+ padding: 0, '12dp'
+ orientation: 'vertical'
+ spacing: '12dp'
+ size_hint: 1, None
+ height: self.minimum_height
+ CreateAccountTextInput:
+ id: text_input_seed
+ size_hint: 1, None
+ height: '110dp'
+ hint_text:
+ _('Enter your seedphrase')
+ on_text: next.disabled = not bool(root._wizard.is_any(self))
+ Label:
+ font_size: '12sp'
+ text_size: self.width, None
+ size_hint: 1, None
+ height: self.texture_size[1]
+ halign: 'justify'
+ valign: 'middle'
+ text:
+ _('If you need additional information, please check '
+ '[color=#0000ff][ref=1]'
+ 'https://electrum.org/faq.html#seed[/ref][/color]')
+ on_ref_press:
+ import webbrowser
+ webbrowser.open('https://electrum.org/faq.html#seed')
+ GridLayout:
+ rows: 1
+ spacing: '12dp'
+ size_hint: 1, None
+ height: self.minimum_height
+ CreateAccountButtonBlue:
+ id: back
+ text: _('Back')
+ root: root
+ CreateAccountButtonGreen:
+ id: next
+ text: _('Next')
+ root: root
+
+
+<InitSeedDialog>
+ spacing: '12dp'
+ GridLayout:
+ id: grid
+ cols: 1
+ pos_hint: {'center_y': .5}
+ size_hint_y: None
+ height: dp(180)
+ orientation: 'vertical'
+ Button:
+ border: 4, 4, 4, 4
+ halign: 'justify'
+ valign: 'middle'
+ font_size: self.width/21
+ text_size: self.width - dp(24), self.height - dp(12)
+ #size_hint: 1, None
+ #height: self.texture_size[1] + dp(24)
+ background_normal: 'atlas://gui/kivy/theming/light/white_bg_round_top'
+ background_down: self.background_normal
+ text: root.message
+ GridLayout:
+ rows: 1
+ size_hint: 1, .7
+ #size_hint_y: None
+ #height: but_seed.texture_size[1] + dp(24)
+ Button:
+ id: but_seed
+ border: 4, 4, 4, 4
+ halign: 'justify'
+ valign: 'middle'
+ font_size: self.width/15
+ text: root.seed_msg
+ text_size: self.width - dp(24), self.height - dp(12)
+ background_normal: 'atlas://gui/kivy/theming/light/lightblue_bg_round_lb'
+ background_down: self.background_normal
+ Button:
+ id: bt
+ size_hint_x: .25
+ background_normal: 'atlas://gui/kivy/theming/light/blue_bg_round_rb'
+ background_down: self.background_normal
+ Image:
+ mipmap: True
+ source: 'atlas://gui/kivy/theming/light/qrcode'
+ size: bt.size
+ center: bt.center
+ #on_release:
+ GridLayout:
+ rows: 1
+ spacing: '12dp'
+ size_hint: 1, None
+ height: self.minimum_height
+ CreateAccountButtonBlue:
+ id: back
+ text: _('Back')
+ root: root
+ CreateAccountButtonGreen:
+ id: confirm
+ text: _('Confirm')
+ root: root
+
+
+<ChangePasswordDialog>
+ padding: '7dp'
+ GridLayout:
+ size_hint_y: None
+ height: self.minimum_height
+ cols: 1
+ CreateAccountTextInput:
+ id: ti_wallet_name
+ hint_text: 'Your Wallet Name'
+ multiline: False
+ on_text_validate:
+ next = ti_new_password if ti_password.disabled else ti_password
+ next.focus = True
+ Widget:
+ size_hint_y: None
+ height: '13dp'
+ CreateAccountTextInput:
+ id: ti_password
+ hint_text: 'Enter old pincode'
+ size_hint_y: None
+ height: 0 if self.disabled else '38sp'
+ password: True
+ disabled: True if root.mode in ('new', 'create', 'restore') else False
+ opacity: 0 if self.disabled else 1
+ multiline: False
+ on_text_validate:
+ ti_new_password.focus = True
+ Widget:
+ size_hint_y: None
+ height: 0 if ti_password.disabled else '13dp'
+ CreateAccountTextInput:
+ id: ti_new_password
+ hint_text: 'Enter new pincode'
+ multiline: False
+ password: True
+ on_text_validate: ti_confirm_password.focus = True
+ Widget:
+ size_hint_y: None
+ height: '13dp'
+ CreateAccountTextInput:
+ id: ti_confirm_password
+ hint_text: 'Confirm pincode'
+ password: True
+ multiline: False
+ on_text_validate: root.validate_new_password()
+ Widget
+ GridLayout:
+ rows: 1
+ spacing: '12dp'
+ size_hint: 1, None
+ height: self.minimum_height
+ CreateAccountButtonBlue:
+ id: back
+ text: _('Back')
+ root: root
+ disabled: True if root.mode[0] == 'r' else self.disabled
+ CreateAccountButtonGreen:
+ id: next
+ text: _('Confirm') if root.mode[0] == 'r' else _('Next')
+ root: root
+
+''')
+
+
+class CreateAccountDialog(EventsDialog):
+ ''' Abstract dialog to be used as the base for all Create Account Dialogs
+ '''
+ crcontent = ObjectProperty(None)
+
+ def __init__(self, **kwargs):
+ super(CreateAccountDialog, self).__init__(**kwargs)
+ self.action = kwargs.get('action')
+ _trigger_size_dialog = Clock.create_trigger(self._size_dialog)
+ Window.bind(size=_trigger_size_dialog,
+ rotation=_trigger_size_dialog)
+ _trigger_size_dialog()
+
+ def _size_dialog(self, dt):
+ app = App.get_running_app()
+ if app.ui_mode[0] == 'p':
+ self.size = Window.size
+ else:
+ #tablet
+ if app.orientation[0] == 'p':
+ #portrait
+ self.size = Window.size[0]/1.67, Window.size[1]/1.4
+ else:
+ self.size = Window.size[0]/2.5, Window.size[1]
+
+ def add_widget(self, widget, index=0):
+ if not self.crcontent:
+ super(CreateAccountDialog, self).add_widget(widget)
+ else:
+ self.crcontent.add_widget(widget, index=index)
+
+
+class CreateRestoreDialog(CreateAccountDialog):
+ ''' Initial Dialog for creating or restoring seed'''
+
+ def on_parent(self, instance, value):
+ if value:
+ app = App.get_running_app()
+ self.ids.but_close.disabled = True
+ self.ids.but_close.opacity = 0
+ self._back = _back = partial(app.dispatch, 'on_back')
+ app.navigation_higherarchy.append(_back)
+
+ def close(self):
+ app = App.get_running_app()
+ if self._back in app.navigation_higherarchy:
+ app.navigation_higherarchy.pop()
+ self._back = None
+ super(CreateRestoreDialog, self).close()
+
+
+class ChangePasswordDialog(CreateAccountDialog):
+
+ message = StringProperty(_('Empty Message'))
+ '''Message to be displayed.'''
+
+ mode = OptionProperty('new',
+ options=('new', 'confirm', 'create', 'restore'))
+ ''' Defines the mode of the password dialog.'''
+
+ def validate_new_password(self):
+ self.ids.next.dispatch('on_release')
+
+ def on_parent(self, instance, value):
+ if value:
+ # change the stepper image used to indicate the current state
+ stepper = self.ids.stepper
+ stepper.opacity = 1
+ t_wallet_name = self.ids.ti_wallet_name
+ if self.mode in ('create', 'restore'):
+ t_wallet_name.text = 'Default Wallet'
+ t_wallet_name.readonly = True
+ #self.ids.ti_new_password.focus = True
+ else:
+ t_wallet_name.text = ''
+ t_wallet_name.readonly = False
+ #t_wallet_name.focus = True
+ stepper.source = 'atlas://gui/kivy/theming/light/stepper_left'
+ self._back = _back = partial(self.ids.back.dispatch, 'on_release')
+ app = App.get_running_app()
+ app.navigation_higherarchy.append(_back)
+
+ def close(self):
+ ids = self.ids
+ ids.ti_wallet_name.text = ""
+ ids.ti_wallet_name.focus = False
+ ids.ti_password.text = ""
+ ids.ti_password.focus = False
+ ids.ti_new_password.text = ""
+ ids.ti_new_password.focus = False
+ ids.ti_confirm_password.text = ""
+ ids.ti_confirm_password.focus = False
+ app = App.get_running_app()
+ if self._back in app.navigation_higherarchy:
+ app.navigation_higherarchy.pop()
+ self._back = None
+ super(ChangePasswordDialog, self).close()
+
+
+class InitSeedDialog(CreateAccountDialog):
+
+ mode = StringProperty('create')
+ ''' Defines the mode for which to optimize the UX. defaults to 'create'.
+
+ Can be one of: 'create', 'restore', 'create_2of2', 'create_2fa'...
+ '''
+
+ seed_msg = StringProperty('')
+ '''Text to be displayed in the TextInput'''
+
+ message = StringProperty('')
+ '''Message to be displayed under seed'''
+
+ seed = ObjectProperty(None)
+
+ def on_parent(self, instance, value):
+ if value:
+ app = App.get_running_app()
+ stepper = self.ids.stepper
+ stepper.opacity = 1
+ stepper.source = 'atlas://gui/kivy/theming/light/stepper_full'
+ self._back = _back = partial(self.ids.back.dispatch, 'on_release')
+ app.navigation_higherarchy.append(_back)
+
+ def close(self):
+ app = App.get_running_app()
+ if self._back in app.navigation_higherarchy:
+ app.navigation_higherarchy.pop()
+ self._back = None
+ super(InitSeedDialog, self).close()
+
+
+class RestoreSeedDialog(CreateAccountDialog):
+
+ def __init__(self, **kwargs):
+ self._wizard = kwargs['wizard']
+ super(RestoreSeedDialog, self).__init__(**kwargs)
+
+ def on_parent(self, instance, value):
+ if value:
+ tis = self.ids.text_input_seed
+ tis.focus = True
+ tis._keyboard.bind(on_key_down=self.on_key_down)
+ stepper = self.ids.stepper
+ stepper.opacity = 1
+ stepper.source = ('atlas://gui/kivy/theming'
+ '/light/stepper_restore_seed')
+ self._back = _back = partial(self.ids.back.dispatch,
+ 'on_release')
+ app = App.get_running_app()
+ app.navigation_higherarchy.append(_back)
+
+ def on_key_down(self, keyboard, keycode, key, modifiers):
+ if keycode[0] in (13, 271):
+ self.on_enter()
+ return True
+
+ def on_enter(self):
+ #self._remove_keyboard()
+ # press next
+ next = self.ids.next
+ if not next.disabled:
+ next.dispatch('on_release')
+
+ def _remove_keyboard(self):
+ tis = self.ids.text_input_seed
+ if tis._keyboard:
+ tis._keyboard.unbind(on_key_down=self.on_key_down)
+ tis.focus = False
+
+ def close(self):
+ self._remove_keyboard()
+ app = App.get_running_app()
+ if self._back in app.navigation_higherarchy:
+ app.navigation_higherarchy.pop()
+ self._back = None
+ super(RestoreSeedDialog, self).close()
diff --git a/gui/kivy/uix/dialogs/installwizard.py b/gui/kivy/uix/dialogs/installwizard.py
@@ -0,0 +1,478 @@
+from electrum import Wallet
+from electrum.i18n import _
+
+from kivy.app import App
+from kivy.uix.widget import Widget
+from kivy.core.window import Window
+from kivy.clock import Clock
+from kivy.factory import Factory
+
+Factory.register('CreateRestoreDialog',
+ module='electrum_gui.kivy.uix.dialogs.create_restore')
+
+import sys
+import threading
+from functools import partial
+import weakref
+
+# global Variables
+app = App.get_running_app()
+
+
+class InstallWizard(Widget):
+ '''Installation Wizard. Responsible for instantiating the
+ creation/restoration of wallets.
+
+ events::
+ `on_wizard_complete` Fired when the wizard is done creating/ restoring
+ wallet/s.
+ '''
+
+ __events__ = ('on_wizard_complete', )
+
+ def __init__(self, config, network, storage):
+ super(InstallWizard, self).__init__()
+ self.config = config
+ self.network = network
+ self.storage = storage
+
+ def waiting_dialog(self, task,
+ msg= _("Electrum is generating your addresses,"
+ " please wait."),
+ on_complete=None):
+ '''Perform a blocking task in the background by running the passed
+ method in a thread.
+ '''
+
+ def target():
+
+ # run your threaded function
+ try:
+ task()
+ except Exception as err:
+ Clock.schedule_once(lambda dt: app.show_error(str(err)))
+
+ # on completion hide message
+ Clock.schedule_once(lambda dt: app.info_bubble.hide(now=True), -1)
+
+ # call completion routine
+ if on_complete:
+ Clock.schedule_once(lambda dt: on_complete())
+
+ app.show_info_bubble(
+ text=msg, icon='atlas://gui/kivy/theming/light/important',
+ pos=Window.center, width='200sp', arrow_pos=None, modal=True)
+ t = threading.Thread(target = target)
+ t.start()
+
+ def get_seed_text(self, ti_seed):
+ text = unicode(ti_seed.text.lower()).strip()
+ text = ' '.join(text.split())
+ return text
+
+ def is_any(self, seed_e):
+ text = self.get_seed_text(seed_e)
+ return (Wallet.is_seed(text) or
+ Wallet.is_mpk(text) or
+ Wallet.is_address(text) or
+ Wallet.is_private_key(text))
+
+ def run(self, action):
+ '''Entry point of our Installation wizard
+ '''
+ if not action:
+ return
+
+ Factory.CreateRestoreDialog(
+ on_release=self.on_creatrestore_complete,
+ action=action).open()
+
+ def on_creatrestore_complete(self, dialog, button):
+ if not button:
+ # soft back or escape button pressed
+ return self.dispatch('on_wizard_complete', None)
+ dialog.close()
+
+ action = dialog.action
+ if button == dialog.ids.create:
+ # create
+ # TODO take from UI instead of hardcoding
+ #t = dialog.wallet_type
+ t = 'standard'
+
+ if t == 'standard':
+ wallet = Wallet(self.storage)
+ action = 'create'
+
+ elif t == '2fa':
+ wallet = Wallet_2of3(self.storage)
+ run_hook('create_cold_seed', wallet, self)
+ self.create_cold_seed(wallet)
+ return
+
+ elif t == '2of2':
+ wallet = Wallet_2of2(self.storage)
+ action = 'create_2of2_1'
+
+ elif t == '2of3':
+ wallet = Wallet_2of3(self.storage)
+ action = 'create_2of3_1'
+
+ if action in ['create_2fa_2', 'create_2of3_2']:
+ wallet = Wallet_2of3(self.storage)
+
+ if action in ['create', 'create_2of2_1',
+ 'create_2fa_2', 'create_2of3_1']:
+ self.password_dialog(wallet=wallet, mode=action)
+
+ elif button == dialog.ids.restore:
+ # restore
+ wallet = None
+ self.restore_seed_dialog(wallet)
+
+ else:
+ self.dispatch('on_wizard_complete', None)
+
+ def restore_seed_dialog(self, wallet):
+ #TODO t currently hardcoded
+ t = 'standard'
+ if t == 'standard':
+ from electrum_gui.kivy.uix.dialogs.create_restore import\
+ RestoreSeedDialog
+ RestoreSeedDialog(
+ on_release=partial(self.on_verify_restore_ok, wallet),
+ wizard=weakref.proxy(self)).open()
+
+ elif t in ['2fa', '2of2']:
+ r = self.multi_seed_dialog(1)
+ if not r:
+ return
+ text1, text2 = r
+ password = self.password_dialog(wallet=wallet)
+ if t == '2of2':
+ wallet = Wallet_2of2(self.storage)
+ elif t == '2of3':
+ wallet = Wallet_2of3(self.storage)
+ elif t == '2fa':
+ wallet = Wallet_2of3(self.storage)
+
+ if Wallet.is_seed(text1):
+ wallet.add_seed(text1, password)
+ if Wallet.is_seed(text2):
+ wallet.add_cold_seed(text2, password)
+ else:
+ wallet.add_master_public_key("cold/", text2)
+
+ elif Wallet.is_mpk(text1):
+ if Wallet.is_seed(text2):
+ wallet.add_seed(text2, password)
+ wallet.add_master_public_key("cold/", text1)
+ else:
+ wallet.add_master_public_key("m/", text1)
+ wallet.add_master_public_key("cold/", text2)
+
+ if t == '2fa':
+ run_hook('restore_third_key', wallet, self)
+
+ wallet.create_account()
+
+ elif t in ['2of3']:
+ r = self.multi_seed_dialog(2)
+ if not r:
+ return
+ text1, text2, text3 = r
+ password = self.password_dialog()
+ wallet = Wallet_2of3(self.storage)
+
+ if Wallet.is_seed(text1):
+ wallet.add_seed(text1, password)
+ if Wallet.is_seed(text2):
+ wallet.add_cold_seed(text2, password)
+ else:
+ wallet.add_master_public_key("cold/", text2)
+
+ elif Wallet.is_mpk(text1):
+ if Wallet.is_seed(text2):
+ wallet.add_seed(text2, password)
+ wallet.add_master_public_key("cold/", text1)
+ else:
+ wallet.add_master_public_key("m/", text1)
+ wallet.add_master_public_key("cold/", text2)
+
+ wallet.create_account()
+
+ def on_verify_restore_ok(self, wallet, _dlg, btn, restore=False):
+ if btn in (_dlg.ids.back, _dlg.ids.but_close) :
+ _dlg.close()
+ Factory.CreateRestoreDialog(
+ on_release=self.on_creatrestore_complete).open()
+ return
+
+ seed = self.get_seed_text(_dlg.ids.text_input_seed)
+ if not seed:
+ return app.show_error(_("No seed!"), duration=.5)
+
+ _dlg.close()
+
+ if Wallet.is_seed(seed):
+ return self.password_dialog(wallet=wallet, mode='restore',
+ seed=seed)
+ elif Wallet.is_mpk(seed):
+ wallet = Wallet.from_mpk(seed, self.storage)
+ elif Wallet.is_address(seed):
+ wallet = Wallet.from_address(seed, self.storage)
+ elif Wallet.is_private_key(seed):
+ wallet = Wallet.from_private_key(seed, self.storage)
+ else:
+ return app.show_error(_('Not a valid seed. App will now exit'),
+ exit=True, modal=True, duration=.5)
+ return
+
+
+ def show_seed(self, wallet=None, instance=None, password=None,
+ wallet_name=None, mode='create', seed=''):
+ if instance and (not wallet or not wallet.seed):
+ return app.show_error(_('No seed'))
+
+ if not seed:
+ try:
+ seed = self.wallet.get_seed(password)
+ except Exception:
+ return app.show_error(_('Incorrect Password'))
+
+ brainwallet = seed
+
+ msg2 = _("[color=#414141]"+\
+ "[b]PLEASE WRITE DOWN YOUR SEED PASS[/b][/color]"+\
+ "[size=9]\n\n[/size]" +\
+ "[color=#929292]If you ever forget your pincode, your seed" +\
+ " phrase will be the [color=#EB984E]"+\
+ "[b]only way to recover[/b][/color] your wallet. Your " +\
+ " [color=#EB984E][b]Bitcoins[/b][/color] will otherwise be" +\
+ " [color=#EB984E][b]lost forever![/b][/color]")
+
+ if wallet.imported_keys:
+ msg2 += "[b][color=#ff0000ff]" + _("WARNING") + "[/color]:[/b] " +\
+ _("Your wallet contains imported keys. These keys cannot" +\
+ " be recovered from seed.")
+
+ def on_ok_press(_dlg, _btn):
+ _dlg.close()
+ mode = _dlg.mode
+ if _btn != _dlg.ids.confirm:
+ if not instance:
+ self.password_dialog(wallet, mode=mode)
+ return
+ # confirm
+ if instance is None:
+ # in initial phase create mode
+ # save seed with password
+ wallet.add_seed(seed, password)
+ sid = None if mode == 'create' else 'hot'
+
+ if mode == 'create':
+ def create(password):
+ wallet.create_accounts(password)
+ wallet.synchronize() # generate first addresses offline
+
+ self.waiting_dialog(partial(create, password),
+ on_complete=partial(self.load_network,
+ wallet, mode=mode))
+ elif mode == 'create_2of2_1':
+ mode = 'create_2of2_2'
+ elif mode == 'create_2of3_1':
+ mode = 'create_2of3_2'
+ elif mode == 'create_2fa_2':
+ mode = 'create_2fa_3'
+
+ if mode == 'create_2of2_2':
+ xpub_hot = wallet.master_public_keys.get("m/")
+ xpub = self.multi_mpk_dialog(xpub_hot, 1)
+ if not xpub:
+ return
+ wallet.add_master_public_key("cold/", xpub)
+ wallet.create_account()
+ self.waiting_dialog(wallet.synchronize)
+
+ if mode == 'create_2of3_2':
+ xpub_hot = wallet.master_public_keys.get("m/")
+ r = self.multi_mpk_dialog(xpub_hot, 2)
+ if not r:
+ return
+ xpub1, xpub2 = r
+ wallet.add_master_public_key("cold/", xpub1)
+ wallet.add_master_public_key("remote/", xpub2)
+ wallet.create_account()
+ self.waiting_dialog(wallet.synchronize)
+
+ if mode == 'create_2fa_3':
+ run_hook('create_remote_key', wallet, self)
+ if not wallet.master_public_keys.get("remote/"):
+ return
+ wallet.create_account()
+ self.waiting_dialog(wallet.synchronize)
+
+
+ from electrum_gui.kivy.uix.dialogs.create_restore import InitSeedDialog
+ InitSeedDialog(message=msg2,
+ seed_msg=brainwallet, on_release=on_ok_press, mode=mode).open()
+
+ def password_dialog(self, wallet=None, instance=None, mode='create',
+ seed=''):
+ """Can be called directly (instance is None)
+ or from a callback (instance is not None)"""
+ app = App.get_running_app()
+
+ if mode != 'create' and wallet and wallet.is_watching_only():
+ return app.show_error('This is a watching only wallet')
+
+ if instance and not wallet.seed:
+ return app.show_error('No seed !!', exit=True, modal=True)
+
+ if instance is not None:
+ if wallet.use_encryption:
+ msg = (
+ _('Your wallet is encrypted. Use this dialog to change" + \
+ " your password.') + '\n' + _('To disable wallet" + \
+ " encryption, enter an empty new password.'))
+ mode = 'confirm'
+ else:
+ msg = _('Your wallet keys are not encrypted')
+ mode = 'new'
+ else:
+ msg = _("Please choose a password to encrypt your wallet keys.") +\
+ '\n' + _("Leave these fields empty if you want to disable" + \
+ " encryption.")
+
+ def on_release(wallet, seed, _dlg, _btn):
+ ti_password = _dlg.ids.ti_password
+ ti_new_password = _dlg.ids.ti_new_password
+ ti_confirm_password = _dlg.ids.ti_confirm_password
+ if _btn != _dlg.ids.next:
+ if mode == 'restore':
+ # back is disabled cause seed is already set
+ return
+ _dlg.close()
+ if not instance:
+ # back on create
+ Factory.CreateRestoreDialog(
+ on_release=self.on_creatrestore_complete).open()
+ return
+
+ # Confirm
+ wallet_name = _dlg.ids.ti_wallet_name.text
+ new_password = unicode(ti_new_password.text)
+ new_password2 = unicode(ti_confirm_password.text)
+
+ if new_password != new_password2:
+ # passwords don't match
+ ti_password.text = ""
+ ti_new_password.text = ""
+ ti_confirm_password.text = ""
+ if ti_password.disabled:
+ ti_new_password.focus = True
+ else:
+ ti_password.focus = True
+ return app.show_error(_('Passwords do not match'), duration=.5)
+
+ if not new_password:
+ new_password = None
+
+ if mode == 'restore':
+ wallet = Wallet.from_seed(seed, self.storage)
+ password = (unicode(ti_password.text)
+ if wallet and wallet.use_encryption else
+ None)
+
+ def on_complete(*l):
+ wallet.create_accounts(new_password)
+ self.load_network(wallet, mode='restore')
+ _dlg.close()
+
+ self.waiting_dialog(lambda: wallet.add_seed(seed, new_password),
+ msg=_("saving seed"),
+ on_complete=on_complete)
+ return
+
+ if not instance:
+ # create mode
+ _dlg.close()
+ seed = wallet.make_seed()
+
+ return self.show_seed(password=new_password, wallet=wallet,
+ wallet_name=wallet_name, mode=mode,
+ seed=seed)
+
+ # change password mode
+ try:
+ seed = wallet.decode_seed(password)
+ except BaseException:
+ return app.show_error(_('Incorrect Password'), duration=.5)
+
+ # test carefully
+ try:
+ wallet.update_password(seed, password, new_password)
+ except BaseException:
+ return app.show_error(_('Failed to update password'), exit=True)
+ else:
+ app.show_info_bubble(
+ text=_('Password successfully updated'), duration=1,
+ pos=_btn.pos)
+ _dlg.close()
+
+
+ if instance is None: # in initial phase
+ self.load_wallet()
+ self.app.update_wallet()
+
+ from electrum_gui.kivy.uix.dialogs.create_restore import ChangePasswordDialog
+ cpd = ChangePasswordDialog(
+ message=msg,
+ mode=mode,
+ on_release=partial(on_release,
+ wallet, seed)).open()
+
+ def load_network(self, wallet, mode='create'):
+ #if not self.config.get('server'):
+ if self.network:
+ if self.network.interfaces:
+ if mode not in ('restore', 'create'):
+ self.network_dialog()
+ else:
+ app.show_error(_('You are offline'))
+ self.network.stop()
+ self.network = None
+
+ if mode in ('restore', 'create'):
+ # auto cycle
+ self.config.set_key('auto_cycle', True, True)
+
+ # start wallet threads
+ wallet.start_threads(self.network)
+
+ if not mode == 'restore':
+ return self.dispatch('on_wizard_complete', wallet)
+
+ def get_text(text):
+ def set_text(*l): app.info_bubble.ids.lbl.text=text
+ Clock.schedule_once(set_text)
+
+ def on_complete(*l):
+ if not self.network:
+ app.show_info(
+ _("This wallet was restored offline. It may contain more"
+ " addresses than displayed."), duration=.5)
+ return self.dispatch('on_wizard_complete', wallet)
+
+ if wallet.is_found():
+ app.show_info(_("Recovery successful"), duration=.5)
+ else:
+ app.show_info(_("No transactions found for this seed"),
+ duration=.5)
+ return self.dispatch('on_wizard_complete', wallet)
+
+ self.waiting_dialog(lambda: wallet.restore(get_text),
+ on_complete=on_complete)
+
+ def on_wizard_complete(self, wallet):
+ pass
diff --git a/gui/kivy/uix/dialogs/new_contact.py b/gui/kivy/uix/dialogs/new_contact.py
@@ -0,0 +1,26 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.cache import Cache
+
+Factory.register('QrScannerDialog', module='electrum_gui.kivy.uix.dialogs.qr_scanner')
+
+class NewContactDialog(Factory.AnimatedPopup):
+
+ def load_qr_scanner(self):
+ self.dismiss()
+ dlg = Cache.get('electrum_widgets', 'QrScannerDialog')
+ if not dlg:
+ dlg = Factory.QrScannerDialog()
+ Cache.append('electrum_widgets', 'QrScannerDialog', dlg)
+ dlg.bind(on_release=self.on_release)
+ dlg.open()
+
+ def on_release(self, instance, uri):
+ self.new_contact(uri=uri)
+
+ def new_contact(self, uri={}):
+ # load NewContactScreen
+ app = App.get_running_app()
+ #app.root.
+ # set contents of uri in the new contact screen
diff --git a/gui/kivy/uix/dialogs/nfc_transaction.py b/gui/kivy/uix/dialogs/nfc_transaction.py
@@ -0,0 +1,32 @@
+class NFCTransactionDialog(AnimatedPopup):
+
+ mode = OptionProperty('send', options=('send','receive'))
+
+ scanner = ObjectProperty(None)
+
+ def __init__(self, **kwargs):
+ # Delayed Init
+ global NFCSCanner
+ if NFCSCanner is None:
+ from electrum_gui.kivy.nfc_scanner import NFCScanner
+ self.scanner = NFCSCanner
+
+ super(NFCTransactionDialog, self).__init__(**kwargs)
+ self.scanner.nfc_init()
+ self.scanner.bind()
+
+ def on_parent(self, instance, value):
+ sctr = self.ids.sctr
+ if value:
+ def _cmp(*l):
+ anim = Animation(rotation=2, scale=1, opacity=1)
+ anim.start(sctr)
+ anim.bind(on_complete=_start)
+
+ def _start(*l):
+ anim = Animation(rotation=350, scale=2, opacity=0)
+ anim.start(sctr)
+ anim.bind(on_complete=_cmp)
+ _start()
+ return
+ Animation.cancel_all(sctr)+
\ No newline at end of file
diff --git a/gui/kivy/uix/dialogs/qr_scanner.py b/gui/kivy/uix/dialogs/qr_scanner.py
@@ -0,0 +1,41 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.lang import Builder
+
+Factory.register('QRScanner', module='electrum_gui.kivy.qr_scanner')
+
+class QrScannerDialog(Factory.EventsDialog):
+
+ def on_symbols(self, instance, value):
+ instance.stop()
+ self.dismiss()
+ uri = App.get_running_app().decode_uri(value[0].data)
+ #address = uri.get('address', 'empty')
+ #label = uri.get('label', '')
+ #amount = uri.get('amount', 0.0)
+ #message = uir.get('message', '')
+ self.dispatch('on_release', uri)
+
+
+Builder.load_string('''
+<QrScannerDialog>
+ title:
+ _(\
+ '[size=18dp]Hold your QRCode up to the camera[/size][size=7dp]\\n[/size]')
+ title_size: '24sp'
+ border: 7, 7, 7, 7
+ size_hint: None, None
+ size: '320dp', '270dp'
+ pos_hint: {'center_y': .53}
+ separator_color: .89, .89, .89, 1
+ separator_height: '1.2dp'
+ title_color: .437, .437, .437, 1
+ background: 'atlas://gui/kivy/theming/light/dialog'
+ on_activate:
+ qrscr.start()
+ qrscr.size = self.size
+ on_deactivate: qrscr.stop()
+ QRScanner:
+ id: qrscr
+ on_symbols: root.on_symbols(*args)
+''')+
\ No newline at end of file
diff --git a/gui/kivy/uix/drawer.py b/gui/kivy/uix/drawer.py
@@ -0,0 +1,257 @@
+'''Drawer Widget to hold the main window and the menu/hidden section that
+can be swiped in from the left. This Menu would be only hidden in phone mode
+and visible in Tablet Mode.
+
+This class is specifically in lined to save on start up speed(minimize i/o).
+'''
+
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import OptionProperty, NumericProperty, ObjectProperty
+from kivy.clock import Clock
+from kivy.lang import Builder
+
+import gc
+
+# delayed imports
+app = None
+
+
+class Drawer(Factory.RelativeLayout):
+ '''Drawer Widget to hold the main window and the menu/hidden section that
+ can be swiped in from the left. This Menu would be only hidden in phone mode
+ and visible in Tablet Mode.
+
+ '''
+
+ state = OptionProperty('closed',
+ options=('closed', 'open', 'opening', 'closing'))
+ '''This indicates the current state the drawer is in.
+
+ :attr:`state` is a `OptionProperty` defaults to `closed`. Can be one of
+ `closed`, `open`, `opening`, `closing`.
+ '''
+
+ scroll_timeout = NumericProperty(200)
+ '''Timeout allowed to trigger the :data:`scroll_distance`,
+ in milliseconds. If the user has not moved :data:`scroll_distance`
+ within the timeout, the scrolling will be disabled and the touch event
+ will go to the children.
+
+ :data:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty`
+ and defaults to 200 (milliseconds)
+ '''
+
+ scroll_distance = NumericProperty('9dp')
+ '''Distance to move before scrolling the :class:`Drawer` in pixels.
+ As soon as the distance has been traveled, the :class:`Drawer` will
+ start to scroll, and no touch event will go to children.
+ It is advisable that you base this value on the dpi of your target
+ device's screen.
+
+ :data:`scroll_distance` is a :class:`~kivy.properties.NumericProperty`
+ and defaults to 20dp.
+ '''
+
+ drag_area = NumericProperty('9dp')
+ '''The percentage of area on the left edge that triggers the opening of
+ the drawer. from 0-1
+
+ :attr:`drag_area` is a `NumericProperty` defaults to 2
+ '''
+
+ hidden_widget = ObjectProperty(None)
+ ''' This is the widget that is hidden in phone mode on the left side of
+ drawer or displayed on the left of the overlay widget in tablet mode.
+
+ :attr:`hidden_widget` is a `ObjectProperty` defaults to None.
+ '''
+
+ overlay_widget = ObjectProperty(None)
+ '''This a pointer to the default widget that is overlayed either on top or
+ to the right of the hidden widget.
+ '''
+
+ def __init__(self, **kwargs):
+ super(Drawer, self).__init__(**kwargs)
+
+ self._triigger_gc = Clock.create_trigger(self._re_enable_gc, .2)
+
+ def toggle_drawer(self):
+ if app.ui_mode[0] == 't':
+ return
+ Factory.Animation.cancel_all(self.overlay_widget)
+ anim = Factory.Animation(x=self.hidden_widget.width
+ if self.state in ('opening', 'closed') else 0,
+ d=.1, t='linear')
+ anim.bind(on_complete = self._complete_drawer_animation)
+ anim.start(self.overlay_widget)
+
+ def _re_enable_gc(self, dt):
+ global gc
+ gc.enable()
+
+ def on_touch_down(self, touch):
+ if self.disabled:
+ return
+
+ if not self.collide_point(*touch.pos):
+ return
+
+ touch.grab(self)
+
+ # disable gc for smooth interaction
+ # This is still not enough while wallet is synchronising
+ # look into pausing all background tasks while ui interaction like this
+ gc.disable()
+
+ global app
+ if not app:
+ app = App.get_running_app()
+
+ # skip on tablet mode
+ if app.ui_mode[0] == 't':
+ return super(Drawer, self).on_touch_down(touch)
+
+ state = self.state
+ touch.ud['send_touch_down'] = False
+ start = 0 #if state[0] == 'c' else self.hidden_widget.right
+ drag_area = self.drag_area\
+ if self.state[0] == 'c' else\
+ (self.overlay_widget.x)
+
+ if touch.x < start or touch.x > drag_area:
+ if self.state == 'open':
+ self.toggle_drawer()
+ return
+ return super(Drawer, self).on_touch_down(touch)
+
+ self._touch = touch
+ Clock.schedule_once(self._change_touch_mode,
+ self.scroll_timeout/1000.)
+ touch.ud['in_drag_area'] = True
+ touch.ud['send_touch_down'] = True
+ return
+
+ def on_touch_move(self, touch):
+ if not touch.grab_current is self:
+ return
+ self._touch = False
+ # skip on tablet mode
+ if app.ui_mode[0] == 't':
+ return super(Drawer, self).on_touch_move(touch)
+
+ if not touch.ud.get('in_drag_area', None):
+ return super(Drawer, self).on_touch_move(touch)
+
+ ov = self.overlay_widget
+ ov.x=min(self.hidden_widget.width,
+ max(ov.x + touch.dx*2, 0))
+
+ #_anim = Animation(x=x, duration=1/2, t='in_out_quart')
+ #_anim.cancel_all(ov)
+ #_anim.start(ov)
+
+ if abs(touch.x - touch.ox) < self.scroll_distance:
+ return
+
+ touch.ud['send_touch_down'] = False
+ Clock.unschedule(self._change_touch_mode)
+ self._touch = None
+ self.state = 'opening' if touch.dx > 0 else 'closing'
+ touch.ox = touch.x
+ return
+
+ def _change_touch_mode(self, *args):
+ if not self._touch:
+ return
+ touch = self._touch
+ touch.ungrab(self)
+ touch.ud['in_drag_area'] = False
+ touch.ud['send_touch_down'] = False
+ self._touch = None
+ super(Drawer, self).on_touch_down(touch)
+ return
+
+ def on_touch_up(self, touch):
+ if not touch.grab_current is self:
+ return
+
+ self._triigger_gc()
+
+ touch.ungrab(self)
+ touch.grab_current = None
+
+ # skip on tablet mode
+ get = touch.ud.get
+ if app.ui_mode[0] == 't':
+ return super(Drawer, self).on_touch_up(touch)
+
+ self.old_x = [1, ] * 10
+ self.speed = sum((
+ (self.old_x[x + 1] - self.old_x[x]) for x in range(9))) / 9.
+
+ if get('send_touch_down', None):
+ # touch up called before moving
+ Clock.unschedule(self._change_touch_mode)
+ self._touch = None
+ Clock.schedule_once(
+ lambda dt: super(Drawer, self).on_touch_down(touch))
+ if get('in_drag_area', None):
+ if abs(touch.x - touch.ox) < self.scroll_distance:
+ anim_to = (0 if self.state[0] == 'c'
+ else self.hidden_widget.width)
+ Factory.Animation(x=anim_to, d=.1).start(self.overlay_widget)
+ return
+ touch.ud['in_drag_area'] = False
+ if not get('send_touch_down', None):
+ self.toggle_drawer()
+ Clock.schedule_once(lambda dt: super(Drawer, self).on_touch_up(touch))
+
+ def _complete_drawer_animation(self, *args):
+ self.state = 'open' if self.state in ('opening', 'closed') else 'closed'
+
+ def add_widget(self, widget, index=1):
+ if not widget:
+ return
+
+ iget = self.ids.get
+ if not iget('hidden_widget') or not iget('overlay_widget'):
+ super(Drawer, self).add_widget(widget)
+ return
+
+ if not self.hidden_widget:
+ self.hidden_widget = self.ids.hidden_widget
+ if not self.overlay_widget:
+ self.overlay_widget = self.ids.overlay_widget
+
+ if self.overlay_widget.children and self.hidden_widget.children:
+ Logger.debug('Drawer: Accepts only two widgets. discarding rest')
+ return
+
+ if not self.hidden_widget.children:
+ self.hidden_widget.add_widget(widget)
+ else:
+ self.overlay_widget.add_widget(widget)
+ widget.x = 0
+
+ def remove_widget(self, widget):
+ if self.overlay_widget.children[0] == widget:
+ self.overlay_widget.clear_widgets()
+ return
+ if widget == self.hidden_widget.children:
+ self.hidden_widget.clear_widgets()
+ return
+
+ def clear_widgets(self):
+ self.overlay_widget.clear_widgets()
+ self.hidden_widget.clear_widgets()
+
+if __name__ == '__main__':
+ from kivy.app import runTouchApp
+ from kivy.lang import Builder
+ runTouchApp(Builder.load_string('''
+Drawer:
+ Button:
+ Button
+'''))+
\ No newline at end of file
diff --git a/gui/kivy/uix/gridview.py b/gui/kivy/uix/gridview.py
@@ -0,0 +1,205 @@
+from kivy.uix.boxlayout import BoxLayout
+from kivy.adapters.dictadapter import DictAdapter
+from kivy.adapters.listadapter import ListAdapter
+from kivy.properties import ObjectProperty, ListProperty, AliasProperty
+from kivy.uix.listview import (ListItemButton, ListItemLabel, CompositeListItem,
+ ListView)
+from kivy.lang import Builder
+from kivy.metrics import dp, sp
+
+Builder.load_string('''
+<GridView>
+ header_view: header_view
+ content_view: content_view
+ BoxLayout:
+ orientation: 'vertical'
+ padding: '0dp', '2dp'
+ BoxLayout:
+ id: header_box
+ orientation: 'vertical'
+ size_hint: 1, None
+ height: '30dp'
+ ListView:
+ id: header_view
+ BoxLayout:
+ id: content_box
+ orientation: 'vertical'
+ ListView:
+ id: content_view
+
+<-HorizVertGrid>
+ header_view: header_view
+ content_view: content_view
+ ScrollView:
+ id: scrl
+ do_scroll_y: False
+ RelativeLayout:
+ size_hint_x: None
+ width: max(scrl.width, dp(sum(root.widths)))
+ BoxLayout:
+ orientation: 'vertical'
+ padding: '0dp', '2dp'
+ BoxLayout:
+ id: header_box
+ orientation: 'vertical'
+ size_hint: 1, None
+ height: '30dp'
+ ListView:
+ id: header_view
+ BoxLayout:
+ id: content_box
+ orientation: 'vertical'
+ ListView:
+ id: content_view
+
+''')
+
+class GridView(BoxLayout):
+ """Workaround solution for grid view by using 2 list view.
+ Sometimes the height of lines is shown properly."""
+
+ def _get_hd_adpt(self):
+ return self.ids.header_view.adapter
+
+ header_adapter = AliasProperty(_get_hd_adpt, None)
+ '''
+ '''
+
+ def _get_cnt_adpt(self):
+ return self.ids.content_view.adapter
+
+ content_adapter = AliasProperty(_get_cnt_adpt, None)
+ '''
+ '''
+
+ headers = ListProperty([])
+ '''
+ '''
+
+ widths = ListProperty([])
+ '''
+ '''
+
+ data = ListProperty([])
+ '''
+ '''
+
+ getter = ObjectProperty(lambda item, i: item[i])
+ '''
+ '''
+ on_context_menu = ObjectProperty(None)
+
+ def __init__(self, **kwargs):
+ self._from_widths = False
+ super(GridView, self).__init__(**kwargs)
+ #self.on_headers(self, self.headers)
+
+ def on_widths(self, instance, value):
+ if not self.get_root_window():
+ return
+ self._from_widths = True
+ self.on_headers(instance, self.headers)
+ self._from_widths = False
+
+ def on_headers(self, instance, value):
+ if not self._from_widths:
+ return
+ if not (value and self.canvas and self.headers):
+ return
+ widths = self.widths
+ if len(self.widths) != len(value):
+ return
+ #if widths is not None:
+ # widths = ['%sdp' % i for i in widths]
+
+ def generic_args_converter(row_index,
+ item,
+ is_header=True,
+ getter=self.getter):
+ cls_dicts = []
+ _widths = self.widths
+ getter = self.getter
+ on_context_menu = self.on_context_menu
+
+ for i, header in enumerate(self.headers):
+ kwargs = {
+ 'padding': ('2dp','2dp'),
+ 'halign': 'center',
+ 'valign': 'middle',
+ 'size_hint_y': None,
+ 'shorten': True,
+ 'height': '30dp',
+ 'text_size': (_widths[i], dp(30)),
+ 'text': getter(item, i),
+ }
+
+ kwargs['font_size'] = '9sp'
+ if is_header:
+ kwargs['deselected_color'] = kwargs['selected_color'] =\
+ [0, 1, 1, 1]
+ else: # this is content
+ kwargs['deselected_color'] = 1, 1, 1, 1
+ if on_context_menu is not None:
+ kwargs['on_press'] = on_context_menu
+
+ if widths is not None: # set width manually
+ kwargs['size_hint_x'] = None
+ kwargs['width'] = widths[i]
+
+ cls_dicts.append({
+ 'cls': ListItemButton,
+ 'kwargs': kwargs,
+ })
+
+ return {
+ 'id': item[-1],
+ 'size_hint_y': None,
+ 'height': '30dp',
+ 'cls_dicts': cls_dicts,
+ }
+
+ def header_args_converter(row_index, item):
+ return generic_args_converter(row_index, item)
+
+ def content_args_converter(row_index, item):
+ return generic_args_converter(row_index, item, is_header=False)
+
+
+ self.ids.header_view.adapter = ListAdapter(data=[self.headers],
+ args_converter=header_args_converter,
+ selection_mode='single',
+ allow_empty_selection=False,
+ cls=CompositeListItem)
+
+ self.ids.content_view.adapter = ListAdapter(data=self.data,
+ args_converter=content_args_converter,
+ selection_mode='single',
+ allow_empty_selection=False,
+ cls=CompositeListItem)
+ self.content_adapter.bind_triggers_to_view(self.ids.content_view._trigger_reset_populate)
+
+class HorizVertGrid(GridView):
+ pass
+
+
+if __name__ == "__main__":
+ from kivy.app import App
+ class MainApp(App):
+
+ def build(self):
+ data = []
+ for i in range(90):
+ data.append((str(i), str(i)))
+ self.data = data
+ return Builder.load_string('''
+BoxLayout:
+ orientation: 'vertical'
+ HorizVertGrid:
+ on_parent: if args[1]: self.content_adapter.data = app.data
+ headers:['Address', 'Previous output']
+ widths: [400, 500]
+
+<Label>
+ font_size: '16sp'
+''')
+ MainApp().run()
diff --git a/gui/kivy/menus.py b/gui/kivy/uix/menus.py
diff --git a/gui/kivy/uix/qrcodewidget.py b/gui/kivy/uix/qrcodewidget.py
@@ -0,0 +1,180 @@
+''' Kivy Widget that accepts data and displas qrcode
+'''
+
+from threading import Thread
+from functools import partial
+
+from kivy.uix.floatlayout import FloatLayout
+
+from kivy.graphics.texture import Texture
+from kivy.properties import StringProperty
+from kivy.properties import ObjectProperty, StringProperty, ListProperty,\
+ BooleanProperty
+from kivy.lang import Builder
+from kivy.clock import Clock
+
+try:
+ import qrcode
+except ImportError:
+ import sys
+ sys.exit("Error: qrcode does not seem to be installed. Try 'sudo pip install qrcode'")
+
+
+
+Builder.load_string('''
+<QRCodeWidget>
+ on_parent: if args[1]: qrimage.source = self.loading_image
+ canvas.before:
+ # Draw white Rectangle
+ Color:
+ rgba: root.background_color
+ Rectangle:
+ size: self.size
+ pos: self.pos
+ canvas.after:
+ Color:
+ rgba: .5, .5, .5, 1 if root.show_border else 0
+ Line:
+ width: dp(1.333)
+ points:
+ self.x + dp(2), self.y + dp(2),\
+ self.right - dp(2), self.y + dp(2),\
+ self.right - dp(2), self.top - dp(2),\
+ self.x + dp(2), self.top - dp(2),\
+ self.x + dp(2), self.y + dp(2)
+ Image
+ id: qrimage
+ pos_hint: {'center_x': .5, 'center_y': .5}
+ allow_stretch: True
+ size_hint: None, None
+ size: root.width * .9, root.height * .9
+''')
+
+class QRCodeWidget(FloatLayout):
+
+ show_border = BooleanProperty(True)
+ '''Whether to show border around the widget.
+
+ :data:`show_border` is a :class:`~kivy.properties.BooleanProperty`,
+ defaulting to `True`.
+ '''
+
+ data = StringProperty(None, allow_none=True)
+ ''' Data using which the qrcode is generated.
+
+ :data:`data` is a :class:`~kivy.properties.StringProperty`, defaulting to
+ `None`.
+ '''
+
+ background_color = ListProperty((1, 1, 1, 1))
+ ''' Background color of the background of the widget.
+
+ :data:`background_color` is a :class:`~kivy.properties.ListProperty`,
+ defaulting to `(1, 1, 1, 1)`.
+ '''
+
+ loading_image = StringProperty('gui/kivy/theming/loading.gif')
+
+ def __init__(self, **kwargs):
+ super(QRCodeWidget, self).__init__(**kwargs)
+ self.addr = None
+ self.qr = None
+ self._qrtexture = None
+
+ def on_data(self, instance, value):
+ if not (self.canvas or value):
+ return
+ img = self.ids.get('qrimage', None)
+
+ if not img:
+ # if texture hasn't yet been created delay the texture updation
+ Clock.schedule_once(lambda dt: self.on_data(instance, value))
+ return
+ img.anim_delay = .05
+ img.source = self.loading_image
+ Thread(target=partial(self.generate_qr, value)).start()
+
+ def generate_qr(self, value):
+ self.set_addr(value)
+ self.update_qr()
+
+ def set_addr(self, addr):
+ if self.addr == addr:
+ return
+ MinSize = 210 if len(addr) < 128 else 500
+ self.setMinimumSize((MinSize, MinSize))
+ self.addr = addr
+ self.qr = None
+
+ def update_qr(self):
+ if not self.addr and self.qr:
+ return
+ QRCode = qrcode.QRCode
+ L = qrcode.constants.ERROR_CORRECT_L
+ addr = self.addr
+ try:
+ self.qr = qr = QRCode(
+ version=None,
+ error_correction=L,
+ box_size=10,
+ border=0,
+ )
+ qr.add_data(addr)
+ qr.make(fit=True)
+ except Exception as e:
+ print e
+ self.qr=None
+ self.update_texture()
+
+ def setMinimumSize(self, size):
+ # currently unused, do we need this?
+ self._texture_size = size
+
+ def _create_texture(self, k, dt):
+ self._qrtexture = texture = Texture.create(size=(k,k), colorfmt='rgb')
+ # don't interpolate texture
+ texture.min_filter = 'nearest'
+ texture.mag_filter = 'nearest'
+
+ def update_texture(self):
+ if not self.addr:
+ return
+
+ matrix = self.qr.get_matrix()
+ k = len(matrix)
+ # create the texture in main UI thread otherwise
+ # this will lead to memory corruption
+ Clock.schedule_once(partial(self._create_texture, k), -1)
+ buff = []
+ bext = buff.extend
+ cr, cg, cb, ca = self.background_color[:]
+ cr, cg, cb = cr*255, cg*255, cb*255
+
+ for r in range(k):
+ for c in range(k):
+ bext([0, 0, 0] if matrix[r][c] else [cr, cg, cb])
+
+ # then blit the buffer
+ buff = ''.join(map(chr, buff))
+ # update texture in UI thread.
+ Clock.schedule_once(lambda dt: self._upd_texture(buff), .1)
+
+ def _upd_texture(self, buff):
+ texture = self._qrtexture
+ if not texture:
+ # if texture hasn't yet been created delay the texture updation
+ Clock.schedule_once(lambda dt: self._upd_texture(buff), .1)
+ return
+ texture.blit_buffer(buff, colorfmt='rgb', bufferfmt='ubyte')
+ img =self.ids.qrimage
+ img.anim_delay = -1
+ img.texture = texture
+ img.canvas.ask_update()
+
+if __name__ == '__main__':
+ from kivy.app import runTouchApp
+ import sys
+ data = str(sys.argv[1:])
+ runTouchApp(QRCodeWidget(data=data))
+
+
diff --git a/gui/kivy/uix/screens.py b/gui/kivy/uix/screens.py
@@ -0,0 +1,300 @@
+from kivy.app import App
+from kivy.cache import Cache
+from kivy.clock import Clock
+from kivy.compat import string_types
+from kivy.properties import (ObjectProperty, DictProperty, NumericProperty,
+ ListProperty)
+from kivy.lang import Builder
+from kivy.factory import Factory
+
+
+# Delayed imports
+app = None
+
+
+class CScreen(Factory.Screen):
+
+ __events__ = ('on_activate', 'on_deactivate', 'on_enter', 'on_leave')
+
+ action_view = ObjectProperty(None)
+
+ def _change_action_view(self):
+ app = App.get_running_app()
+ action_bar = app.root.manager.current_screen.ids.action_bar
+ _action_view = self.action_view
+
+ if (not _action_view) or _action_view.parent:
+ return
+ action_bar.clear_widgets()
+ action_bar.add_widget(_action_view)
+
+ def on_enter(self):
+ # FIXME: use a proper event don't use animation time of screen
+ Clock.schedule_once(lambda dt: self.dispatch('on_activate'), .25)
+
+ def on_activate(self):
+ Clock.schedule_once(lambda dt: self._change_action_view())
+
+ def on_leave(self):
+ self.dispatch('on_deactivate')
+
+ def on_deactivate(self):
+ Clock.schedule_once(lambda dt: self._change_action_view())
+
+ def load_screen(self, screen_name):
+ content = self.content
+ if not content:
+ Builder.load_file('gui/kivy/uix/ui_screens/{}.kv'.format(screen_name))
+ if screen_name.endswith('send'):
+ content = Factory.ScreenSendContent()
+ elif screen_name.endswith('receive'):
+ content = Factory.ScreenReceiveContent()
+ content.ids.toggle_qr.state = 'down'
+ self.content = content
+ self.add_widget(content)
+ Factory.Animation(opacity=1, d=.25).start(content)
+ return
+ if screen_name.endswith('receive'):
+ content.mode = 'qr'
+ else:
+ content.mode = 'address'
+
+
+class EScreen(Factory.EffectWidget, CScreen):
+
+ background_color = ListProperty((0.929, .929, .929, .929))
+
+ speed = NumericProperty(0)
+
+ effect_flex_scroll = '''
+uniform float speed;
+
+vec4 effect(vec4 color, sampler2D texture, vec2 tex_coords, vec2 coords)
+{{
+ return texture2D(
+ texture,
+ vec2(tex_coords.x + sin(
+ tex_coords.y * 3.1416 / .2 + 3.1416 / .5
+ ) * speed, tex_coords.y));
+}}
+'''
+ def __init__(self, **kwargs):
+ super(EScreen, self).__init__(**kwargs)
+ self.old_x = [1, ] * 10
+ self._anim = Factory.Animation(speed=0, d=.22)
+ from kivy.uix.effectwidget import AdvancedEffectBase
+ self.speed = 0
+ self.scrollflex = AdvancedEffectBase(
+ glsl=self.effect_flex_scroll,
+ uniforms={'speed': self.speed}
+ )
+ self._trigger_straighten = Clock.create_trigger(
+ self.straighten_screen, .15)
+
+ def on_speed(self, *args):
+ value = max(-0.05, min(0.05, float("{0:.5f}".format(args[1]))))
+ self.scrollflex.uniforms['speed'] = value
+
+ def on_parent(self, instance, value):
+ if value:
+ value.bind(x=self.screen_moving)
+
+ def screen_moving(self, instance, value):
+ self.old_x.append(value/self.width)
+ self.old_x.pop(0)
+ self.speed = sum(((self.old_x[x + 1] - self.old_x[x]) for x in range(9))) / 9.
+ self._anim.cancel_all(self)
+ self._trigger_straighten()
+
+ def straighten_screen(self, dt):
+ self._anim.start(self)
+
+
+class ScreenDashboard(EScreen):
+ ''' Dashboard screen: Used to display the main dashboard.
+ '''
+
+ tab = ObjectProperty(None)
+
+ def __init__(self, **kwargs):
+ self.ra_dialog = None
+ super(ScreenDashboard, self).__init__(**kwargs)
+
+ def show_tx_details(self, item):
+ ra_dialog = Cache.get('electrum_widgets', 'RecentActivityDialog')
+ if not ra_dialog:
+ Factory.register('RecentActivityDialog',
+ module='electrum_gui.kivy.uix.dialogs.carousel_dialog')
+ Factory.register('GridView',
+ module='electrum_gui.kivy.uix.gridview')
+ ra_dialog = ra_dialog = Factory.RecentActivityDialog()
+ Cache.append('electrum_widgets', 'RecentActivityDialog', ra_dialog)
+ ra_dialog.item = item
+ ra_dialog.open()
+
+
+class ScreenAddress(CScreen):
+ '''This is the dialog that shows a carousel of the currently available
+ addresses.
+ '''
+
+ labels = DictProperty({})
+ '''
+ '''
+
+ tab = ObjectProperty(None)
+ ''' The tab associated With this Carousel
+ '''
+
+
+class ScreenPassword(Factory.Screen):
+
+ __events__ = ('on_release', 'on_deactivate', 'on_activate')
+
+ def on_activate(self):
+ app = App.get_running_app()
+ action_bar = app.root.main_screen.ids.action_bar
+ action_bar.add_widget(self._action_view)
+
+ def on_deactivate(self):
+ self.ids.password.text = ''
+
+ def on_release(self, *args):
+ pass
+
+
+class MainScreen(Factory.Screen):
+ pass
+
+
+class ScreenSend(EScreen):
+ pass
+
+
+class ScreenReceive(EScreen):
+ pass
+
+
+class ScreenContacts(EScreen):
+
+ def add_new_contact(self):
+ dlg = Cache.get('electrum_widgets', 'NewContactDialog')
+ if not dlg:
+ dlg = NewContactDialog()
+ Cache.append('electrum_widgets', 'NewContactDialog', dlg)
+ dlg.open()
+
+
+class CSpinner(Factory.Spinner):
+ '''CustomDropDown that allows fading out the dropdown
+ '''
+
+ def _update_dropdown(self, *largs):
+ dp = self._dropdown
+ cls = self.option_cls
+ if isinstance(cls, string_types):
+ cls = Factory.get(cls)
+ dp.clear_widgets()
+ def do_release(option):
+ Clock.schedule_once(lambda dt: dp.select(option.text), .25)
+ for value in self.values:
+ item = cls(text=value)
+ item.bind(on_release=do_release)
+ dp.add_widget(item)
+
+
+class TabbedCarousel(Factory.TabbedPanel):
+ '''Custom TabbedOanel using a carousel used in the Main Screen
+ '''
+
+ carousel = ObjectProperty(None)
+
+ def animate_tab_to_center(self, value):
+ scrlv = self._tab_strip.parent
+ if not scrlv:
+ return
+
+ idx = self.tab_list.index(value)
+ if idx == 0:
+ scroll_x = 1
+ elif idx == len(self.tab_list) - 1:
+ scroll_x = 0
+ else:
+ self_center_x = scrlv.center_x
+ vcenter_x = value.center_x
+ diff_x = (self_center_x - vcenter_x)
+ try:
+ scroll_x = scrlv.scroll_x - (diff_x / scrlv.width)
+ except ZeroDivisionError:
+ pass
+ mation = Factory.Animation(scroll_x=scroll_x, d=.25)
+ mation.cancel_all(scrlv)
+ mation.start(scrlv)
+
+ def on_current_tab(self, instance, value):
+ if value.text == 'default_tab':
+ return
+ self.animate_tab_to_center(value)
+
+ def on_index(self, instance, value):
+ current_slide = instance.current_slide
+ if not hasattr(current_slide, 'tab'):
+ return
+ tab = current_slide.tab
+ ct = self.current_tab
+ try:
+ if ct.text != tab.text:
+ carousel = self.carousel
+ carousel.slides[ct.slide].dispatch('on_leave')
+ self.switch_to(tab)
+ carousel.slides[tab.slide].dispatch('on_enter')
+ except AttributeError:
+ current_slide.dispatch('on_enter')
+
+ def switch_to(self, header):
+ # we have to replace the functionality of the original switch_to
+ if not header:
+ return
+ if not hasattr(header, 'slide'):
+ header.content = self.carousel
+ super(TabbedCarousel, self).switch_to(header)
+ try:
+ tab = self.tab_list[-1]
+ except IndexError:
+ return
+ self._current_tab = tab
+ tab.state = 'down'
+ return
+
+ carousel = self.carousel
+ self.current_tab.state = "normal"
+ header.state = 'down'
+ self._current_tab = header
+ # set the carousel to load the appropriate slide
+ # saved in the screen attribute of the tab head
+ slide = carousel.slides[header.slide]
+ if carousel.current_slide != slide:
+ carousel.current_slide.dispatch('on_leave')
+ carousel.load_slide(slide)
+ slide.dispatch('on_enter')
+
+ def add_widget(self, widget, index=0):
+ if isinstance(widget, Factory.CScreen):
+ self.carousel.add_widget(widget)
+ return
+ super(TabbedCarousel, self).add_widget(widget, index=index)
+
+
+class ELTextInput(Factory.TextInput):
+ '''Custom TextInput used in main screens for numeric entry
+ '''
+
+ def insert_text(self, substring, from_undo=False):
+ if not from_undo:
+ if self.input_type == 'numbers':
+ numeric_list = map(str, range(10))
+ if '.' not in self.text:
+ numeric_list.append('.')
+ if substring not in numeric_list:
+ return
+ super(ELTextInput, self).insert_text(substring, from_undo=from_undo)
diff --git a/gui/kivy/uix/ui_screens/mainscreen.kv b/gui/kivy/uix/ui_screens/mainscreen.kv
@@ -0,0 +1,1405 @@
+#:import _ electrum.i18n._
+#:import Cache kivy.cache.Cache
+#:import Factory kivy.factory.Factory
+#:set font_light 'data/fonts/Roboto-Condensed.ttf'
+#:set btc_symbol unichr(171)
+#:set mbtc_symbol unichr(187)
+
+<WalletActionPrevious@ActionPrevious>
+ app_icon: 'atlas://gui/kivy/theming/light/' + ('wallets' if app.ui_mode[0] != 't' else 'tab_btn')
+ with_previous: False
+ size_hint: None, 1
+ mipmap: True
+ on_release: app.root.children[0].toggle_drawer()
+
+<CloseButton@IconButton>
+ source: 'atlas://gui/kivy/theming/light/closebutton'
+ opacity: 1 if self.state == 'normal' else .75
+ size_hint: None, None
+ size: '27dp', '27dp'
+
+#######################
+# Screen Contacts
+#######################
+<ContactImage@Widget>:
+ source: 'atlas://gui/kivy/theming/light/contact_avatar'
+ size_hint_x: None
+ width: self.height
+ canvas:
+ Color:
+ rgba: 1, 1, 1, 1
+ Ellipse:
+ source: root.source
+ size: self.width + dp(6), self.height + dp(6)
+ pos: self.x - dp(3), self.y - dp(3)
+ Ellipse:
+ source: 'atlas://gui/kivy/theming/light/contact_overlay'
+ size: self.width + dp(11), self.height + dp(11)
+ pos: self.x - dp(5.5), self.y - dp(5.5)
+
+<ContactLabel@Label>
+ color: .305, .309, .309, 1
+ text_size: self.size
+ halign: 'left'
+ valign: 'middle'
+
+<ContactSeperator@Widget>
+ canvas.before:
+ Color:
+ rgba: .890, .890, .890, 1
+ Rectangle:
+ size: self.size
+ pos: self.x, self.y + dp(9)
+ size_hint: None, None
+ size: '1dp', '22dp'
+ pos_hint_y: .5
+
+<ContactTextInput@TextInput>
+ background_normal: self.background_down
+ background_down: 'atlas://gui/kivy/theming/light/tab_btn'
+ size_hint_y: None
+ height: '22dp'
+
+<ContactBitLogo@Image>
+ source: 'atlas://gui/kivy/theming/light/bit_logo'
+ size_hint_x: None
+ width: '32dp'
+
+<ContactItem@BoxLayout>
+ address: ''
+ label: ''
+ tx_amt: 0
+ size_hint_y: None
+ height: '65dp'
+ padding: dp(12)
+ spacing: dp(5)
+ canvas.before:
+ Color:
+ rgba: 1, 1, 1, 1
+ Rectangle:
+ size: self.size
+ pos: self.pos
+ ContactImage:
+ id: contact_image
+ Widget:
+ size_hint_x: None
+ width: '9dp'
+ ContactLabel:
+ id: contact_label
+ text: root.label
+ ContactSeperator:
+ ContactBitLogo:
+
+<ScreenContacts>
+ name: 'contacts'
+ on_activate:
+ if not self.action_view:\
+ self.action_view = app.root.main_screen.ids.tabs.ids.screen_dashboard.action_view
+ BoxLayout:
+ orientation: 'vertical'
+ spacing: '1dp'
+ ContactTextInput:
+ ScrollView:
+ canvas.before:
+ Color:
+ rgba: .8901, .8901, .8901, 1
+ Rectangle:
+ size: self.size
+ pos: self.pos
+ GridLayout:
+ cols: 1
+ id: contact_container
+ size_hint_y: None
+ height: self.minimum_height
+ spacing: '1dp'
+
+<SendActionView@ActionView>
+ foreground_color: (.466, .466, .466, 1)
+ color_active: (0.235, .588, .89, 1)
+ WalletActionPrevious:
+ id: action_previous
+ width: but_star.width
+ ActionButton:
+ id: action_logo
+ important: True
+ size_hint: 1, 1
+ markup: True
+ mipmap: True
+ bold: True
+ markup: True
+ color: 1, 1, 1, 1
+ text:
+ "[color=#777777][sub] [sup][size=9dp]{}[/size][/sup][/sub]{}[/color]"\
+ .format(app.base_unit, app.status)
+ font_size: '22dp'
+ minimum_width: '1dp'
+ Butt_star:
+ id: but_star
+ on_release:
+ if self.state == 'down':\
+ app.show_info_bubble(\
+ text='[b]Expert mode on[/b]\n you can now select your address',\
+ icon='atlas://gui/kivy/theming/light/star_big_inactive',\
+ duration=1, arrow_pos='', width='250dp')
+
+<ScreenSend>
+ name: 'send'
+ action_view: Factory.SendActionView()
+ on_activate:
+ root.load_screen('screensend')
+ on_deactivate:
+ self.content.ids.amount_e.focus = False
+ self.content.ids.payto_e.focus = False
+ self.content.ids.message_e.focus = False
+
+<SendToggle@ToggleButton>
+ source: ''
+ group: 'transfer_type'
+ markup: False
+ bold: True
+ border: 4, 4, 4, 4
+ background_normal: self.background_down
+ color:
+ (.140, .140, .140, 1) if self.state == 'down' else (.796, .796, .796, 1)
+ canvas.after:
+ Color:
+ rgba: 1, 1, 1, 1
+ Image:
+ source: root.source
+ color: root.color
+ size: '30dp', '30dp'
+ center_x: root.center_x - ((root.texture_size[0]/2)+(self.width/1.5))
+ center_y: root.center_y
+
+<BlueSpinner@BoxLayout>
+ foreground_color: 1, 1, 1, 1
+ spacing: '9dp'
+ text: ''
+ values: ('', )
+ icon: ''
+ Image:
+ source: root.icon
+ size_hint: None, None
+ size: '22dp', '22dp'
+ pos_hint: {'center_y': .5}
+ OppositeSpinner:
+ color: root.foreground_color
+ background_normal: 'atlas://gui/kivy/theming/light/action_group_light'
+ markup: False
+ shorten: True
+ font_size: '16dp'
+ size_hint: 1, .7
+ pos_hint: {'center_y': .5}
+ text: root.text
+ text_size: self.size
+ halign: 'left'
+ valign: 'middle'
+ on_text:
+ root.text = args[1]
+ values: root.values
+
+<AddressSelector@BlueSpinner>
+ icon: 'atlas://gui/kivy/theming/light/globe'
+ values: app.wallet.addresses()
+ text: _("Select Your address")
+
+<WalletSelector@BlueSpinner>
+ icon: 'atlas://gui/kivy/theming/light/wallet'
+ values: ('default Wallet',)
+ text: _('Select your wallet')
+
+<SendReceiveToggle@BoxLayout>
+ padding: '5dp', '5dp'
+ size_hint: 1, None
+ height: '45dp'
+ canvas.before:
+ Color:
+ rgba: 1, 1, 1, 1
+ BorderImage:
+ border: 12, 12, 12, 12
+ source: 'atlas://gui/kivy/theming/light/card'
+ size: self.width + dp(3), self.height
+ pos: self.x - dp(1.5), self.y
+
+<SendReceiveCardTop@GridLayout>
+ canvas.before:
+ BorderImage:
+ border: 9, 9, 9, 9
+ source: 'atlas://gui/kivy/theming/light/card_top'
+ size: self.size
+ pos:self.pos
+ padding: '12dp', '22dp', '12dp', 0
+ cols: 1
+ size_hint: 1, None
+ height: '120dp'
+ spacing: '4dp'
+
+<SendReceiveBlueBottom@GridLayout>
+ canvas.before:
+ Color:
+ rgba: .238, .585, .878, 1
+ BorderImage:
+ border: 9, 9, 9, 9
+ source: 'atlas://gui/kivy/theming/light/card_bottom'
+ size: self.size
+ pos: self.pos
+ Color:
+ rgba: 1, 1, 1, 1
+
+ item_height: dp(42)
+ foreground_color: .843, .914, .972, 1
+ cols: 1
+ padding: '12dp', 0
+
+
+<ScreenReceive>
+ name: 'receive'
+ action_view: Factory.ReceiveActionView()
+ on_activate:
+ root.load_screen('screenreceive')
+ on_deactivate:
+ self.content.ids.amount_e.focus = False
+
+<ReceiveActionView@ActionView>
+ WalletActionPrevious:
+ id: action_previous
+ width: '32dp'
+ ActionButton:
+ id: action_logo
+ important: True
+ size_hint: 1, 1
+ markup: True
+ mipmap: True
+ bold: True
+ markup: True
+ color: 1, 1, 1, 1
+ text:
+ "[color=#777777][sub] [sup][size=9dp]{}[/size][/sup][/sub]{}[/color]"\
+ .format(app.base_unit, app.status)
+ font_size: '22dp'
+ minimum_width: '1dp'
+ Butt_star:
+ id: but_star
+ on_release:
+ if self.state == 'down':\
+ app.show_info_bubble(\
+ text='[b]Expert mode on[/b]\n you can now select your address',\
+ icon='atlas://gui/kivy/theming/light/star_big_inactive',\
+ duration=1, arrow_pos='', width='250dp')
+
+###############################################
+## Wallet Management
+###############################################
+
+<WalletManagement@ScrollView>
+ canvas.before:
+ Color:
+ rgba: .145, .145, .145, 1
+ Rectangle:
+ size: root.size
+ pos: root.pos
+ VGridLayout:
+ Wallets:
+ id: wallets_section
+ Plugins:
+ id: plugins_section
+ Commands:
+ id: commands_section
+
+<WalletManagementItem@BoxLayout>
+
+<Header@WalletManagementItem>
+
+<Wallets@VGridLayout>
+ Header
+
+<Plugins@VGridLayout>
+ Header
+
+<Commands@VGridLayout>
+ Header
+
+<StripLayout>
+ padding: 0, 0, 0, 0
+
+<TabbedCarousel>
+ carousel: carousel
+ do_default_tab: False
+ Carousel:
+ scroll_timeout: 190
+ anim_type: 'out_quart'
+ min_move: .05
+ anim_move_duration: .1
+ anim_cancel_duration: .54
+ scroll_distance: '10dp'
+ on_index: root.on_index(*args)
+ id: carousel
+
+<CScreen>
+ content: None
+ state: 'deactivated'
+ on_enter: self.state = 'loading'
+ on_activate:
+ self.state = 'activated'
+ on_leave: self.state = 'unloading'
+ on_deactivate: self.state = 'deactivated'
+ color: 1, 1, 1, 1
+ Image:
+ source: 'atlas://gui/kivy/theming/light/logo_atom_dull'
+ size_hint: (None, None)
+ pos_hint: {'center_x': .5, 'y': .02}
+ allow_stretch: True
+ size: ('32dp', '32dp')
+ Label:
+ id: screen_label
+ size: root.size
+ font_size: '45sp'
+ color: root.color
+ text: '' if root.state == 'activated' or root.content else 'Loading...'
+
+<EScreen>
+ background_color: .929, .929, .929 ,1
+ effects: [self.scrollflex]
+
+<OppositeDropDown@DropDown>
+ #auto_width: False
+ size_hint: None, None
+ size: self.container.minimum_size if self.container else (0, 0)
+ on_container: if args[1]: self.container.padding = '4dp', '4dp', '4dp', '4dp'
+ canvas.before:
+ Color:
+ rgba: 1, 1, 1, 1
+ BorderImage:
+ pos:self.pos
+ border: 20, 20, 20, 20
+ source: 'atlas://gui/kivy/theming/light/dropdown_background'
+ size: self.size
+
+<LightOptions@SpinnerOption>
+ font_size: '14sp'
+ border: 4, 4, 4, 4
+ color: 0.439, 0.439, 0.439, .8
+ background_normal: 'atlas://gui/kivy/theming/light/action_button_group'
+ background_down: 'atlas://gui/kivy/theming/light/overflow_btn_dn'
+ size_hint_y: None
+ height: '48dp'
+ text_size: self.size[0] - dp(20), self.size[1]
+ halign: 'left'
+ valign: 'middle'
+ shorten: True
+ on_press:
+ ddn = self.parent.parent
+ Factory.Animation(opacity=0, d=.25).start(ddn)
+
+
+<OppositeSpinner@CSpinner>
+ dropdown_cls: Factory.OppositeDropDown
+ option_cls: Factory.LightOptions
+ border: 20, 20, 9, 9
+ background_normal: 'atlas://gui/kivy/theming/light/action_group_dark'
+ background_down: self.background_normal
+ values: ('Copy to clipboard', 'Send Payment')
+ size_hint: None, 1
+ width: '12dp'
+ on_release:
+ ddn = self._dropdown
+ ddn.opacity = 0
+ Factory.Animation(opacity=1, d=.25).start(ddn)
+
+<NewContactDialog>
+ title: _('[size=7dp] \n[/size] Add a Contact[size=7dp]\n[/size]')
+ title_size: '24sp'
+ border: 7, 7, 7, 7
+ size_hint: None, None
+ size: '320dp', '235dp'
+ pos_hint: {'center_y': .53}
+ separator_color: .89, .89, .89, 1
+ separator_height: '1.2dp'
+ title_color: .437, .437, .437, 1
+ background: 'atlas://gui/kivy/theming/light/dialog'
+ padding: 0, 0
+ on_parent:
+ self.content.padding = 0, 0, 0, 0
+ self.content.parent.parent.padding = 2, 12, 2, 2
+ self.content.parent.parent.spacing = 0, -2
+ BoxLayout:
+ id: bl
+ spacing: '1.2dp'
+ canvas:
+ Color:
+ rgba: .901, .901, .901, 1
+ Rectangle:
+ size: self.size
+ pos: self.pos
+ ContactButton:
+ source: 'atlas://gui/kivy/theming/light/qrcode'
+ text: _('QR Scan')
+ on_release: root.load_qr_scanner()
+ ContactButton:
+ id: but_contact
+ source: 'atlas://gui/kivy/theming/light/manualentry'
+ text: 'Manual Enrty'
+ on_release:
+ root.new_contact()
+ FloatLayout:
+ size_hint: None, None
+ size: 0, 0
+ CloseButton:
+ id: but_close
+ top: root.top - dp(5)
+ right: root.right - dp(5)
+ on_release: root.dismiss()
+
+
+<DialogButton@Button>
+ border: 12, 12, 12, 12
+ background_normal: 'atlas://gui/kivy/theming/light/dialog'
+ background_down: ''
+ background_color: (.94, .94, .94, 1) if self.state == 'normal' else (.191, .496, .742, 1)
+
+
+<ContactButton@DialogButton>
+ source: ''
+ color: 0, 0, 0, 0
+ item_color: (.211, .211, .211, 1) if root.state == 'normal' else (1, 1, 1, 1)
+ Image:
+ id: img
+ allow_stretch: True
+ color: root.item_color
+ mipmap: True
+ source: root.source
+ center: (root.center, self.size)[0]
+ size: root.width/2., root.height/2.
+ Label:
+ text: root.text
+ color: root.item_color
+ size: img.width, self.texture_size[1]
+ center: root.center_x, img.y - (self.height/2)
+
+
+<CarouselHeader>
+ border: 0, 0, 0, 0
+ text: repr(self)
+ color: 0, 0, 0, 0
+ background_normal: 'atlas://gui/kivy/theming/light/carousel_selected'
+ background_down: 'atlas://gui/kivy/theming/light/carousel_deselected'
+ on_state:
+ o = .5 if args[1] == 'down' else 1
+ anim=Factory.Animation(opacity=o, d=.2).start(self)
+
+<CarouselIndicator@TabbedCarousel>
+ tab_pos: 'bottom_mid'
+ tab_height: '32dp'
+ tab_width: self.tab_height
+ background_image: 'atlas://data/images/defaulttheme/action_item'
+ strip_border: 0, 0, 0, 0
+
+<-CarouselDialog>
+ header_color: '#707070ff'
+ text_color: 0.701, 0.701, 0.701, 1
+ title_size: '13sp'
+ title: ''
+ separator_color: 0.89, 0.89, 0.89, 1
+ background: 'atlas://gui/kivy/theming/light/tab_btn'
+ carousel_content: carousel_content
+ canvas.before:
+ Color:
+ rgba: 0, 0, 0, .9
+ Rectangle:
+ size: Window.size
+ pos: 0, 0
+ Color:
+ rgba: 1, 1, 1, 1
+ BorderImage:
+ border: 12, 12, 12, 12
+ source: 'atlas://gui/kivy/theming/light/dialog'
+ size: root.width, root.height - self.carousel_content.tab_height if self.carousel_content else 0
+ pos: root.x, self.y + self.carousel_content.tab_height if self.carousel_content else 10
+ BoxLayout:
+ orientation: 'vertical'
+ GridLayout:
+ cols: 1
+ size_hint: 1, None
+ height: self.minimum_height
+ padding: 0, '7sp'
+ Label:
+ font_size: root.title_size
+ text: u'[color={}]{}[/color]'.format(root.header_color, root.title)
+ text_size: self.width, None
+ halign: 'left'
+ size_hint: 1, None
+ height: self.texture_size[1]
+ CardSeparator:
+ color: root.separator_color
+ height: root.separator_height
+ FloatLayout:
+ size_hint: None, None
+ size: 0, 0
+ CloseButton:
+ id: but_close
+ top: root.top - dp(10)
+ right: root.right - dp(10)
+ on_release: root.dismiss()
+ CarouselIndicator:
+ id: carousel_content
+
+<WalletAddressesDialog>
+ wallet_name: 'Default Wallet'
+ on_parent: if args[1]: self.children[0].padding = '15dp', '6dp', '15dp', 0
+ title_color: .437, .437, .437
+ title_size: '23sp'
+ separator_height: '1dp'
+ size_hint: None, None
+ width: min(Window.width - dp(27), dp(360))
+ height: min(Window.height - dp(70), self.width * 1.5)
+ title: '[size=5sp] \n[/size]{}[size=9sp]\n'.format(self.wallet_name)
+
+<ScreenAddress>
+ name: 'addresses'
+ BoxLayout:
+ size_hint: 1, .95
+ pos_hint: {'top':1}
+ orientation: 'vertical'
+ padding: 0, '2.2dp'
+ Label:
+ markup: False
+ color: 0, 0, 0, .3
+ text: 'Your bitcoin address:'
+ font_size: '15sp'
+ text_size: self.width, None
+ halign: 'left'
+ size_hint: 1, None
+ height: self.texture_size[1]
+ shorten: True
+ BoxLayout:
+ orientation: 'vertical'
+ spacing: dp(5)
+ BoxLayout:
+ size_hint: 1, None
+ spacing: dp(5)
+ height: '27dp'
+ OppositeSpinner:
+ id: btn_address
+ markup: False
+ color: 0.439, 0.439, 0.439, 1
+ font_size: '14sp'
+ text_size: self.width - dp(22), self.height
+ shorten: True
+ halign: 'left'
+ valign: 'middle'
+ size_hint: .8, .8
+ pos_hint: {'center_y': .5}
+ on_text:
+ if args[1]: qr.data = app.encode_uri(root.labels[args[1]])
+ FloatLayout
+ id: fl_paste
+ size_hint: None, 1
+ width: '37dp'
+ Button:
+ right: fl_paste.right
+ y: fl_paste.y
+ size_hint: None, None
+ height: '37dp'
+ width: self.height
+ border: 2, 2, 2, 2
+ background_color: .705, .705, .705, 1.5 if self.state == 'normal' else 2
+ background_normal: 'atlas://gui/kivy/theming/light/paste_icon'
+ background_down: self.background_normal
+ on_release:
+ app.copy(root.labels[btn_address.text])
+ app.show_info_bubble(\
+ text='Copied', width='50dp', arrow_pos='', duration=3,\
+ pos=(Window.width/2, root.parent.top + dp(18)))
+ QRCodeWidget:
+ id: qr
+ show_border: False
+
+<CurrencyLabel@Label>
+ color: .698, .701, .701, 1
+ font_size: '14sp'
+ text_size: self.width, None
+ halign: 'left'
+
+<CurrencySpinner@OppositeSpinner>
+ size_hint_x: None
+ width: '60dp'
+ text_size: self.width, None
+ font_size: '18sp'
+ halign: 'left'
+ color: .698, .701, .701, 1
+
+<SelectionDialog>
+ border: 7, 7, 7, 7
+ size_hint: None, None
+ size: '320dp', '200dp'
+ pos_hint: {'center_y': .53}
+ separator_color: .89, .89, .89, 1
+ separator_height: '1.1dp'
+ title_color: .437, .437, .437, 1
+ background: 'atlas://gui/kivy/theming/light/dialog'
+ on_parent: self.content.parent.parent.padding = '12dp', 0, '12dp', '12dp'
+ RelativeLayout:
+ orientation: 'vertical'
+ padding: 0, '6dp', 0, 0
+ RelativeLayout:
+ id: container
+ BoxLayout
+ size_hint_y: None
+ height: '48dp'
+ pos_hint: {'y':0}
+ DialogButton:
+ id: btn_cancel
+ text: _('Cancel')
+ markup: False
+ color: .439, .439, .439, 1
+ background_color:
+ (.235, .588, .882, 1) if self.state[0] == 'd'\
+ else (.890, .890, .890, 1)
+ on_release:
+ root.dismiss()
+ Widget:
+ size_hint_x: None
+ width: '24dp'
+ DialogButton:
+ id: btn_ok
+ text: _('Ok')
+ background_color:
+ (.235, .588, .882, 1) if self.state[0] == 'n'\
+ else (.890, .890, .890, 1)
+ on_release:
+ root.dispatch('on_release', self)
+ FloatLayout:
+ size_hint: None, None
+ size: 0, 0
+ CloseButton:
+ id: but_close
+ top: root.height - dp(18)
+ right: root.width - dp(18)
+ on_release: root.dismiss()
+
+
+<CurrencySelectionDialog@SelectionDialog>
+ title: '[size=9dp] \n[/size]Currency Selection[size=7dp]\n[/size]'
+ title_size: '24sp'
+ on_activate:
+ spinner_exchanges.text = app.exchanger.use_exchange
+ spinner_currencies.text = app.exchanger.currency
+ on_release:
+ app.exchanger.currency = spinner_currencies.text
+ app.exchanger.use_exchange = spinner_exchanges.text
+ root.dismiss()
+ BoxLayout
+ size_hint_y: None
+ height: '24dp'
+ spacing: '24dp'
+ pos_hint: {'top': .95, 'x': 0}
+ CurrencyLabel:
+ text: _('Currency')
+ size_hint_x: .4
+ CurrencyLabel:
+ id: lbl_source
+ text: _('Exchange Source')
+ BoxLayout:
+ spacing: '24dp'
+ size_hint_y: None
+ height: '32dp'
+ pos_hint: {'x': 0, 'top': .8}
+ CurrencySpinner:
+ id: spinner_currencies
+ text: app.exchanger.currency
+ values: app.currencies
+ Widget:
+ CurrencySpinner:
+ id: spinner_exchanges
+ text: app.exchanger.use_exchange
+ pos: lbl_source.x, spinner_currencies.y
+ width: '140dp'
+ height: spinner_currencies.height
+ values: app.exchanger.exchanges
+
+<RecentActivityDialog>
+ on_parent:
+ if args[1]:\
+ self.children[0].padding = '15dp', '6dp', '15dp', 0
+ is_mine: True
+ amount: '0.00'
+ amount_color: '#000000ff'
+ quote_text: '0'
+ address: u''
+ address_known: unicode(self.address or self.address[1:]) in app.wallet.addressbook + self.labels.keys()
+ confirmations: 0
+ date: '0/0/0'
+ fee: _('unknown')
+ labels: {}
+ status: 'unknown'
+ separator_height: '1dp'
+ time: _('00:00')
+ tx_hash: None
+ title:
+ u'[size=9] \n[/size]'\
+ u'[size={sz}sp][color={clr}]'.format(sz=25.7 if self.width > dp(300) else 22, clr=root.header_color) + \
+ _(u'You ') + (_(u'sent') if self.is_mine else _(u'received')) + u'[/color]'\
+ '[color={clr}] [font={fnt}]{smbl}[/font]{amt}[/color][/size]\n'.format(\
+ clr=root.amount_color, smbl=btc_symbol if app.base_unit == 'BTC' else mbtc_symbol, fnt=font_light, amt=root.amount[:9]) + \
+ _(u'About ') + root.quote_text + _(u' at transaction time') + \
+ u'[size={}dp] \n[/size]'.format(5 if self.width > dp(300) else 1)
+ size_hint: None, None
+ width: min(Window.width - dp(27), dp(320))
+ height: max(grid.height + dp(120), dp(320))
+ font_size: '13sp'
+ CScreen:
+ tab: screen_details
+ GridLayout
+ id: grid
+ padding: 0, 0, 0, '12dp'
+ spacing: '18dp'
+ cols: 1
+ size_hint: 1, None
+ height: self.minimum_height
+ pos_hint: {'top':1}
+ GridLayout:
+ cols: 1
+ size_hint_y: None
+ height: '39sp'
+ spacing: '5dp'
+ CardLabel:
+ color: root.text_color
+ font_size: root.font_size
+ text: _('To') if root.is_mine else _('From')
+ BoxLayout:
+ id: bl_address
+ spacing: '4dp'
+ size_hint_y: None
+ height: '32dp'
+ padding: 0, 0, 0, '9dp'
+ OppositeSpinner:
+ id: btn_address
+ markup: False
+ color: 0.45, 0.45, 0.45, 1
+ font_name: font_light
+ font_size: '15sp'
+ text: root.address
+ shorten: True
+ size_hint: None, 1
+ text_size: self.width - dp(30), self.height
+ halign: 'left'
+ width: min(self._label.get_extents(self.text)[0] + dp(32), bl_address.width)
+ on_release:
+ self._dropdown.auto_width = False
+ self._dropdown.width = max(self.width, dp(140))
+ on_text:
+ if args[1] != root.address[1:]:\
+ root.dropdown_selected(args[1]);\
+ self.text = root.address
+ GridLayout:
+ cols: 2
+ spacing: '18dp'
+ size_hint: 1, None
+ height: self.minimum_height
+ CardLabel:
+ color: root.text_color
+ font_size: root.font_size
+ text:
+ _('Date') + '[color={}][size=4dp]\n\n[/size]'\
+ '[size=18dp]{}[/size][/color]'.format(\
+ root.header_color, root.date)
+ CardLabel:
+ color: root.text_color
+ font_size: root.font_size
+ text:
+ _('Status') +\
+ '[size=4dp]\n\n[/size]'\
+ '[color={}][size=18dp]{}[/size][/color]'.format(\
+ '#009900' if root.status == 'Validated' else root.header_color,\
+ root.status)
+ CardLabel:
+ color: root.text_color
+ font_size: root.font_size
+ text:
+ _('Time') +\
+ '[size=4dp]\n\n[/size]'\
+ '[color={}][size=18dp]{}[/size][/color]'.format(\
+ root.header_color, root.time)
+ CardLabel:
+ color: root.text_color
+ font_size: root.font_size
+ text:
+ _('Confirmations') +\
+ '[size=4dp]\n\n[/size]'\
+ '[color={}][size=18dp]{}'\
+ '[/size][/color]'.format(root.header_color, root.confirmations)
+ GridLayout:
+ id: fl
+ size_hint: 1, None
+ height: self.minimum_height
+ cols: 1
+ BoxLayout:
+ size_hint: 1, None
+ height: '48sp' if self.opacity else 0
+ opacity: 1 if root.is_mine else 0
+ CardLabel
+ size_hint: 1, 1
+ text_size: self.size
+ valign: 'top'
+ font_size: root.font_size
+ color: root.text_color
+ markup: True
+ text:
+ _('Transaction Fees') +\
+ '[size=4dp]\n\n[/size]'\
+ '[color={}][size=18dp]{}'\
+ '[/size][/color]'.format(root.header_color, root.fee)
+ CardLabel:
+ size_hint: 1, 1
+ text_size: self.size
+ valign: 'top'
+ font_size: root.font_size
+ color: root.text_color
+ text: _('Wallet')
+ markup: True
+ DialogButton:
+ id: btn_dialog
+ size_hint: 1, None
+ height: '48sp' if self.opacity else 0
+ opacity: 0 if root.is_mine or root.address_known else 1
+ background_color:
+ (.191, .496, .742, 1) if self.state == 'normal'\
+ else (.933, .933, .933, 1)
+ color:
+ (1, 1, 1, 1) if self.state == 'normal'\
+ else (.218, .218, .218, 1)
+ disabled: False if self.opacity else True
+ text: _('Add address to contacts')
+ on_release:
+ root.dismiss()
+ app.save_new_contact(root.address[1:], '')
+ CScreen:
+ tab: transaction_id
+ color: .5, .5, .5, .5
+ on_activate: root.activate_screen_transactionid(self)
+ CScreen:
+ id: list_inputs
+ color: .5, .5, .5, .5
+ data: ''
+ tab: inputs
+ on_enter: root.activate_screen_inputs(self)
+ CScreen:
+ id: list_outputs
+ color: .5, .5, .5, .5
+ data: ''
+ tab: outputs
+ on_enter: root.activate_screen_outputs(self)
+
+ # define the tab headers here
+ CarouselHeader:
+ id: screen_details
+ slide: 0
+ CarouselHeader:
+ id: transaction_id
+ slide: 1
+ CarouselHeader:
+ id: inputs
+ slide: 2
+ CarouselHeader:
+ id: outputs
+ slide: 3
+
+<RecentActivityScrOutputs@GridView>
+ pos_hint:{'top': 1}
+ size_hint_y: .9
+ headers: [_('Address'), _('Amount')]
+ widths:[root.width*.63, root.width*.36]
+
+<RecentActivityScrInputs@GridView>
+ pos_hint:{'top': 1}
+ size_hint_y: .9
+ headers: [_('Address'), _('Previous output')]
+ widths:[root.width*.63, root.width*.36]
+
+<RecentActivityScrTransID@GridLayout>
+ cols: 1
+ padding: '5dp'
+ spacing: '2dp'
+ text_color: 1, 1, 1
+ tx_hash: None
+ carousel_content: None
+ CardLabel:
+ color: root.text_color
+ font_size: '13dp'
+ text: _('Transaction ID :')
+ ELTextInput:
+ readonly: True
+ text: root.tx_hash if root.tx_hash else ''
+ size_hint_y: None
+ height: '30dp'
+ BoxLayout:
+ padding: 0, '9pt', 0, 0
+ orientation: 'vertical'
+ spacing: '5dp'
+ DialogButton:
+ background_color:
+ (.191, .496, .742, 1) if self.state == 'normal'\
+ else (.933, .933, .933, 1)
+ color:
+ (1, 1, 1, 1) if self.state == 'normal'\
+ else (.218, .218, .218, 1)
+ text: 'Inputs >>'
+ size_hint_y: None
+ height: '38dp'
+ on_release:
+ root.carousel_content.carousel.load_next()
+ DialogButton:
+ text: 'Outputs >>>'
+ background_color:
+ (.191, .496, .742, 1) if self.state == 'normal'\
+ else (.933, .933, .933, 1)
+ color:
+ (1, 1, 1, 1) if self.state == 'normal'\
+ else (.218, .218, .218, 1)
+ size_hint_y: None
+ height: '38dp'
+ on_release:
+ carousel = root.carousel_content.carousel
+ carousel.load_slide(carousel.slides[3])
+ Widget
+
+
+################################
+## Cards (under Dashboard)
+################################
+
+<Card@GridLayout>
+ cols: 1
+ padding: '12dp' , '22dp', '12dp' , '12dp'
+ spacing: '12dp'
+ size_hint: 1, None
+ height: max(100, self.minimum_height)
+ canvas.before:
+ Color:
+ rgba: 1, 1, 1, 1
+ BorderImage:
+ border: 18, 18, 18, 18
+ source: 'atlas://gui/kivy/theming/light/card'
+ size: self.size
+ pos: self.pos
+
+<CardLabel@Label>
+ color: 0.45, 0.45, 0.45, 1
+ size_hint: 1, None
+ text: ''
+ text_size: self.width, None
+ height: self.texture_size[1]
+ halign: 'left'
+ valign: 'top'
+
+<CardButton@Button>
+ background_normal: 'atlas://gui/kivy/theming/light/card_btn'
+ bold: True
+ font_size: '10sp'
+ color: 0.699, 0.699, 0.699, 1
+ size_hint: None, None
+ size: self.texture_size[0] + dp(32), self.texture_size[1] + dp(7)
+
+<CardSeparator@Widget>
+ size_hint: 1, None
+ height: dp(1)
+ color: .909, .909, .909, 1
+ canvas:
+ Color:
+ rgba: root.color if root.color else (0, 0, 0, 0)
+ Rectangle:
+ size: self.size
+ pos: self.pos
+
+<CardItem@ButtonBehavior+GridLayout>
+ canvas.before:
+ Color:
+ rgba: 0.192, .498, 0.745, 1 if self.state == 'down' else 0
+ Rectangle
+ size: self.size
+ pos: self.x, self.y + dp(5)
+ cols: 1
+ padding: '2dp', '2dp'
+ spacing: '2dp'
+ size_hint: 1, None
+ height: self.minimum_height
+
+<RecentActivityItem@CardItem>
+ icon: 'atlas://gui/kivy/theming/light/important'
+ address:'no address set'
+ amount: '+0.00'
+ balance: 'xyz'# balance_after
+ amount_color: '#DB3627' if float(self.amount) < 0 else '#2EA442'
+ confirmations: 0
+ date: '0/0/0'
+ quote_text: '.'
+
+ spacing: '9dp'
+ on_release:
+ dash = app.root.main_screen.ids.tabs.ids.screen_dashboard
+ dash.show_tx_details(root)
+ BoxLayout:
+ size_hint: 1, None
+ spacing: '8dp'
+ height: '32dp'
+ Image:
+ id: icon
+ source: root.icon
+ size_hint: None, 1
+ width: self.height *.54
+ mipmap: True
+ BoxLayout:
+ orientation: 'vertical'
+ Widget
+ CardLabel:
+ shorten: True
+ text: root.address
+ markup: False
+ text_size: self.size
+ CardLabel:
+ color: .699, .699, .699, 1
+ text: root.date
+ font_size: '12sp'
+ Widget
+ CardLabel:
+ halign: 'right'
+ font_size: '13sp'
+ size_hint: None, 1
+ width: '90sp'
+ markup: True
+ font_name: font_light
+ text:
+ u'[color={amount_color}]{sign}{symbol}{amount}[/color]\n'\
+ u'[color=#B2B3B3][size=12sp]{qt}[/size]'\
+ u'[/color]'.format(amount_color=root.amount_color,\
+ amount=root.amount[1:], qt=root.quote_text, sign=root.amount[0],\
+ symbol=btc_symbol if app.base_unit == 'BTC' else mbtc_symbol)
+ CardSeparator
+
+<CardRecentActivity@Card>
+ BoxLayout:
+ size_hint: 1, None
+ height: lbl.height
+ CardLabel:
+ id: lbl
+ text: _('RECENT ACTIVITY')
+ CardButton:
+ id: btn_see_all
+ disabled: True if not self.opacity else False
+ text: _('SEE ALL')
+ font_size: '12sp'
+ on_release: app.update_history_tab(see_all=True)
+ GridLayout:
+ id: content
+ spacing: '7dp'
+ cols: 1
+ size_hint: 1, None
+ height: self.minimum_height
+ CardSeparator
+
+<CardPaymentRequest@Card>
+ CardLabel:
+ text: _('PAYMENT REQUEST')
+ CardSeparator:
+
+<CardStatusInfo@Card>
+ padding: '12dp' , '12dp'
+ status: app.status
+ quote_text: ''
+ unconfirmed: ''
+ cols: 2
+ FloatLayout
+ anchor_x: 'left'
+ size_hint: 1, None
+ height: '82dp'
+ IconButton:
+ mipmap: True
+ pos_hint: {'x': 0, 'center_y': .45}
+ color: .90, .90, .90, 1
+ source: 'atlas://gui/kivy/theming/light/qrcode'
+ size_hint: None, .85
+ width: self.height
+ on_release:
+ dlg = Cache.get('electrum_widgets', 'WalletAddressesDialog')
+
+ if not dlg:\
+ Factory.register('WalletAddressesDialog', module='electrum_gui.kivy.uix.dialogs.carousel_dialog');\
+ dlg = Factory.WalletAddressesDialog();\
+ Cache.append('electrum_widgets', 'WalletAddressesDialog', dlg)
+
+ dlg.open()
+ CardLabel:
+ id: top_label
+ halign: 'right'
+ valign: 'top'
+ bold: True
+ pos_hint: {'top': 1, 'right': 1}
+ font_name: font_light
+ balance_in_numbers: bool(ord(root.status[0]) not in range(ord('A'), ord('z')))
+ font_size: '50sp' if self.balance_in_numbers else '30sp'
+ text_size: self.width, root.height/2
+ text:
+ u'[color=#4E4F4F]{}{}[/color]'\
+ .format('' if not self.balance_in_numbers else\
+ (btc_symbol if app.base_unit == 'BTC' else mbtc_symbol), root.status)
+ BoxLayout
+ pos_hint: {'y': 0, 'right': 1}
+ spacing: '5dp'
+ CardLabel
+ halign: 'right'
+ markup: True
+ font_size: '22dp'
+ font_name: font_light
+ text: u'[color=#c3c3c3]{}[/color]'.format(root.quote_text)
+ IconButton
+ color: .698, .698, .698, 1
+ source: 'atlas://gui/kivy/theming/light/gear'
+ size_hint_y: None
+ height: '28dp'
+ opacity: .5 if self.state == 'down' else 1
+ on_release:
+ dlg = Cache.get('electrum_widgets', 'CurrencySelectionDialog')
+
+ if not dlg:\
+ Factory.register('SelectionDialog', module='electrum_gui.kivy.uix.dialogs');\
+ dlg = Factory.CurrencySelectionDialog();\
+ Cache.append('electrum_widgets', 'CurrencySelectionDialog', dlg)
+
+ dlg.open()
+
+<ActionDropDown>
+ auto_width: False
+ on_size: if not self.auto_width: self.width = dp(190)
+ on_container: self.container.padding = '4dp', '4dp', '4dp', '4dp'
+ on_parent:
+ if args[1]:\
+ self.opacity = 0;\
+ anim = Factory.Animation(opacity=1, d=.25);\
+ anim.start(self)
+ canvas.before:
+ Color:
+ rgba: 1, 1, 1, 1
+ BorderImage:
+ pos:self.pos
+ border: 20, 20, 20, 20
+ source: 'atlas://gui/kivy/theming/light/overflow_background'
+ size: self.size
+
+<ActionItem>
+ color: 0.235, 0.239, 0.239, 1
+
+<ActionButton>:
+ border: 4, 0, 0, 0
+ background_down: 'atlas://gui/kivy/theming/light/overflow_btn_dn'
+
+<OverflowButton@ActionButton>
+ text_size: dp(172), None
+ last: False
+ halign: 'left'
+ valign: 'middle'
+ overflow: None
+ background_normal:
+ 'atlas://gui/kivy/theming/light/' +\
+ ('action_button_group'\
+ if (self.inside_group and not self.last) else 'tab_btn')
+ on_press:
+ ddn = self.overflow._dropdown
+ Factory.Animation.cancel_all(ddn)
+ anim = Factory.Animation(opacity=0, d=.25)
+ anim.bind(on_complete=ddn.dismiss)
+ anim.start(ddn)
+
+<DashboardActionView@ActionView>
+ WalletActionPrevious:
+ id: action_previous
+ size_hint_x: None
+ width: action_preferences.width + action_contact.width
+ ActionButton:
+ id: action_logo
+ important: True
+ pos_hint: {'center_y': .5}
+ size_hint: 1, .93
+ bold: True
+ icon: 'atlas://gui/kivy/theming/light/logo'
+ background_down: self.background_normal
+ minimum_width: '1dp'
+ ActionButton:
+ id: action_contact
+ important: True
+ width: '25dp'
+ icon: 'atlas://gui/kivy/theming/light/add_contact'
+ text: 'Add Contact'
+ on_release:
+ Factory.register('NewContactDialog', module='electrum_gui.kivy.uix.dialogs.new_contact')
+ dlg = Factory.NewContactDialog().open()
+
+ ActionOverflow:
+ id: action_preferences
+ background_down: 'atlas://gui/kivy/theming/light/overflow_btn_dn'
+ border: 0, 0, 0, 0
+ overflow_image: 'atlas://gui/kivy/theming/light/settings'
+ width: '40dp'
+ size_hint_x: None
+ canvas.after:
+ Color:
+ rgba: 1, 1, 1, 1
+ OverflowButton:
+ text: _('Settings')
+ overflow: action_preferences
+ OverflowButton:
+ text: _('Help')
+ last: True
+ overflow: action_preferences
+
+<ScreenDashboard>
+ name: 'dashboard'
+ action_view: Factory.DashboardActionView()
+ content: content
+ ScrollView:
+ id: content
+ do_scroll_x: False
+ GridLayout
+ id: grid
+ cols: 1 #if root.width < root.height else 2
+ size_hint: 1, None
+ height: self.minimum_height
+ padding: '12dp'
+ spacing: '12dp'
+ GridLayout:
+ cols: 1
+ size_hint: 1, None
+ height: self.minimum_height
+ spacing: '12dp'
+ orientation: 'vertical'
+ CardStatusInfo:
+ id: status_card
+ CardPaymentRequest:
+ id: payment_card
+ CardRecentActivity:
+ id: recent_activity_card
+
+
+<CleanHeader@TabbedPanelHeader>
+ border: 0, 0, 4, 0
+ markup: False
+ color: (.188, 0.505, 0.854, 1) if self.state == 'down' else (0.636, 0.636, 0.636, 1)
+ text_size: self.size
+ halign: 'center'
+ valign: 'middle'
+ bold: True
+ font_size: '12.5sp'
+ background_normal: 'atlas://gui/kivy/theming/light/tab_btn'
+ background_disabled_normal: 'atlas://gui/kivy/theming/light/tab_btn_disabled'
+ background_down: 'atlas://gui/kivy/theming/light/tab_btn_pressed'
+ canvas.before:
+ Color:
+ rgba: 1, 1, 1, .7
+ Rectangle:
+ size: self.size
+ pos: self.x + 1, self.y - 1
+ texture: self.texture
+
+<ScreenTabs@Screen>
+ TabbedCarousel:
+ id: panel
+ tab_height: '48dp'
+ background_image: 'atlas://gui/kivy/theming/light/tab'
+ strip_image: 'atlas://gui/kivy/theming/light/tab_strip'
+ strip_border: dp(4), 0, dp(2), 0
+ ScreenDashboard:
+ id: screen_dashboard
+ tab: tab_dashboard
+ ScreenSend:
+ id: screen_send
+ tab: tab_send
+ ScreenReceive:
+ id: screen_receive
+ tab: tab_receive
+ ScreenContacts:
+ id: screen_contacts
+ tab: tab_contacts
+ CleanHeader:
+ id: tab_dashboard
+ text: _('DASHBOARD')
+ slide: 0
+ CleanHeader:
+ id: tab_send
+ text: _('SEND')
+ slide: 1
+ CleanHeader:
+ id: tab_receive
+ text: _('RECEIVE')
+ slide: 2
+ CleanHeader:
+ id: tab_contacts
+ text: _('CONTACTS')
+ slide: 3
+
+<MainScreen>
+ name: 'main_screen'
+ canvas.before:
+ Color:
+ rgba: 0.917, 0.917, 0.917, 1
+ Rectangle:
+ size: self.size
+ pos: self.pos
+ BoxLayout:
+ orientation: 'vertical'
+ ActionBar:
+ id: action_bar
+ size_hint: 1, None
+ height: '48dp'
+ border: 4, 4, 4, 4
+ background_image: 'atlas://gui/kivy/theming/light/action_bar'
+ ScreenManager:
+ id: manager
+ ScreenTabs:
+ id: tabs
+ name: "tabs"
+ #ScreenPassword:
+ # id: password
+ # name: 'password
+
+<Drawer>
+ overlay_widget: overlay_widget
+ RelativeLayout:
+ id: hidden_widget
+ size_hint: None, None
+ width:
+ (root.width * .877) if app.ui_mode[0] == 'p'\
+ else root.width * .35 if app.orientation[0] == 'l'\
+ else root.width * .10
+ height: root.height
+ canvas.before:
+ Color:
+ rgba: .176, .176, .176, 1
+ Rectangle:
+ size: self.size
+ pos: self.pos
+ Color
+ rgba: 1, 1, 1, 1
+ BorderImage
+ border: 0, 32, 0, 0
+ source: 'atlas://gui/kivy/theming/light/shadow_right'
+ size: root.overlay_widget.x if root.overlay_widget else self.width, self.height
+ RelativeLayout:
+ id: overlay_widget
+ size_hint: None, None
+ x: hidden_widget.width if app.ui_mode[0] == 't' else 0
+ size_hint: None, None
+ width: (root.width - self.x) if app.ui_mode[0] == 't' else root.width
+ height: root.height
+
+#######################################################
+## This is our child of the root widget of the app
+#######################################################
+Drawer
+ size_hint: None, None
+ size: Window.size
+ WalletManagement
+ id: wallet_management
+ ScreenManager:
+ # Screen manager for screens meant to change everything including
+ # ActionBar, currently we only have the main screen here.
+ id: manager
+ MainScreen+
\ No newline at end of file
diff --git a/gui/kivy/uix/ui_screens/screenreceive.kv b/gui/kivy/uix/ui_screens/screenreceive.kv
@@ -0,0 +1,138 @@
+#:import Decimal decimal.Decimal
+
+<ScreenReceiveContent@BoxLayout>
+ opacity: 0
+ padding: '12dp', '12dp', '12dp', '12dp'
+ spacing: '12dp'
+ mode: 'qr'
+ orientation: 'vertical'
+ on_parent:
+ if args[1]:\
+ first_address = app.wallet.addresses()[0];\
+ qr.data = app.encode_uri(first_address,\
+ amount=amount_e.text,\
+ label=app.wallet.labels.get(first_address, first_address),\
+ message='') if app.wallet.addresses() else ''
+ SendReceiveToggle
+ SendToggle:
+ id: toggle_qr
+ text: 'QR'
+ state: 'down' if root.mode == 'qr' else 'normal'
+ source: 'atlas://gui/kivy/theming/light/qrcode'
+ background_down: 'atlas://gui/kivy/theming/light/btn_send_address'
+ on_release:
+ if root.mode == 'qr': root.mode = 'nr'
+ root.mode = 'qr'
+ SendToggle:
+ id: toggle_nfc
+ text: 'NFC'
+ state: 'down' if root.mode == 'nfc' else 'normal'
+ source: 'atlas://gui/kivy/theming/light/nfc'
+ background_down: 'atlas://gui/kivy/theming/light/btn_send_nfc'
+ on_release:
+ if root.mode == 'nfc': root.mode = 'nr'
+ root.mode = 'nfc'
+ GridLayout:
+ id: grid
+ cols: 1
+ #size_hint: 1, None
+ #height: self.minimum_height
+ SendReceiveCardTop
+ height: '110dp'
+ BoxLayout:
+ size_hint: 1, None
+ height: '42dp'
+ rows: 1
+ Label:
+ color: amount_e.foreground_color
+ bold: True
+ text_size: self.size
+ valign: 'bottom'
+ font_size: '22sp'
+ text:
+ u'[font={fnt}]{smbl}[/font]'.\
+ format(smbl=btc_symbol if app.base_unit == 'BTC' else mbtc_symbol, fnt=font_light)
+ size_hint_x: .25
+ ELTextInput:
+ id: amount_e
+ input_type: 'number'
+ multiline: False
+ bold: True
+ font_size: '50sp'
+ foreground_color: .308, .308, .308, 1
+ background_normal: 'atlas://gui/kivy/theming/light/tab_btn'
+ pos_hint: {'top': 1.5}
+ size_hint: .7, None
+ height: '67dp'
+ hint_text: 'Amount'
+ text: '0.0'
+ CardSeparator
+ BoxLayout:
+ size_hint: 1, None
+ height: '32dp'
+ spacing: '5dp'
+ Label:
+ color: lbl_quote.color
+ font_size: '12dp'
+ text: 'Ask to scan the QR below'
+ text_size: self.size
+ halign: 'left'
+ valign: 'middle'
+ Label:
+ id: lbl_quote
+ font_size: '12dp'
+ size_hint: .5, 1
+ color: .761, .761, .761, 1
+ text: u'= {}'.format(app.create_quote_text(Decimal(float(amount_e.text)), mode='symbol')) if amount_e.text else u'0'
+ text_size: self.size
+ halign: 'right'
+ valign: 'middle'
+ SendReceiveBlueBottom
+ id: blue_bottom
+ padding: '12dp', 0, '12dp', '12dp'
+ WalletSelector:
+ id: wallet_selection
+ foreground_color: blue_bottom.foreground_color
+ size_hint: 1, None
+ height: blue_bottom.item_height
+ CardSeparator
+ opacity: wallet_selection.opacity
+ color: blue_bottom.foreground_color
+ AddressSelector:
+ id: address_selection
+ foreground_color: blue_bottom.foreground_color
+ opacity: 1 if app.expert_mode else 0
+ size_hint: 1, None
+ height: blue_bottom.item_height if app.expert_mode else 0
+ on_text:
+ if not args[1].startswith('Select'):\
+ qr.data = app.encode_uri(args[1],\
+ amount=amount_e.text,\
+ label=app.wallet.labels.get(args[1], args[1]),\
+ message='')
+ CardSeparator
+ opacity: address_selection.opacity
+ color: blue_bottom.foreground_color
+ Widget:
+ size_hint_y: None
+ height: dp(10)
+ FloatLayout
+ id: bl
+ QRCodeWidget:
+ id: qr
+ size_hint: None, 1
+ width: min(self.height, bl.width)
+ pos_hint: {'center': (.5, .5)}
+ on_touch_down:
+ if self.collide_point(*args[1].pos):\
+ app.show_info_bubble(icon=self.ids.qrimage.texture, text='texture')
+ CreateAccountButtonGreen:
+ background_color: (1, 1, 1, 1) if self.disabled else ((.258, .80, .388, 1) if self.state == 'normal' else (.203, .490, .741, 1))
+ text: _('Goto next step') if app.wallet.seed else _('Create unsigned transaction')
+ size_hint_y: None
+ height: '38dp'
+ disabled: True if wallet_selection.opacity == 0 else False
+ on_release:
+ message = 'sending {} {} to {}'.format(\
+ app.base_unit, amount_e.text, payto_e.text)
+ app.gui.main_gui.do_send(self, message=message)+
\ No newline at end of file
diff --git a/gui/kivy/uix/ui_screens/screensend.kv b/gui/kivy/uix/ui_screens/screensend.kv
@@ -0,0 +1,232 @@
+#:import Decimal decimal.Decimal
+
+<TextInputSendBlue@TextInput>
+ padding: '5dp'
+ size_hint: 1, None
+ height: '27dp'
+ pos_hint: {'center_y':.5}
+ multiline: False
+ hint_text_color: self.foreground_color
+ foreground_color: .843, .914, .972, 1
+ background_color: 1, 1, 1, 1
+ background_normal: 'atlas://gui/kivy/theming/light/tab_btn'
+ background_active: 'atlas://gui/kivy/theming/light/textinput_active'
+
+<TransactionFeeDialog@SelectionDialog>
+ return_obj: None
+ min_fee: app.format_amount(app.wallet.fee)
+ title:
+ '[size=9dp] \n[/size]Transaction Fee[size=9dp]\n'\
+ '[color=#ADAEAE]Minimum is BTC {}[/color][/size]'.format(self.min_fee)
+ title_size: '24sp'
+ on_activate:
+ ti_fee.focus = True
+ if self.return_obj:\
+ ti_fee.text = "BTC " + self.return_obj.amt
+ on_deactivate: ti_fee.focus = False
+ on_release:
+ if self.return_obj and ti_fee.text:\
+ txt = ti_fee.text;\
+ spc = txt.rfind(' ') + 1;\
+ txt = '' if spc == 0 else txt[spc:];\
+ num = 0 if not txt else float(txt);\
+ self.return_obj.amt = max(self.min_fee, txt)
+ root.dismiss()
+ ELTextInput
+ id: ti_fee
+ size_hint: 1, None
+ height: '34dp'
+ multiline: False
+ on_text_validate: root.dispatch('on_release', self)
+ pos_hint: {'center_y': .7}
+ text: "BTC " + root.min_fee
+ input_type: 'number'
+
+<ScreenSendContent@BoxLayout>
+ opacity: 0
+ padding: '12dp', '12dp', '12dp', '12dp'
+ spacing: '12dp'
+ orientation: 'vertical'
+ mode: 'address'
+ SendReceiveToggle:
+ SendToggle:
+ id: toggle_address
+ text: 'ADDRESS'
+ group: 'send_type'
+ state: 'down' if root.mode == 'address' else 'normal'
+ source: 'atlas://gui/kivy/theming/light/globe'
+ background_down: 'atlas://gui/kivy/theming/light/btn_send_address'
+ on_release:
+ if root.mode == 'address': root.mode = 'fc'
+ root.mode = 'address'
+ SendToggle:
+ id: toggle_nfc
+ text: 'NFC'
+ group: 'send_type'
+ state: 'down' if root.mode == 'nfc' else 'normal'
+ source: 'atlas://gui/kivy/theming/light/nfc'
+ background_down: 'atlas://gui/kivy/theming/light/btn_send_nfc'
+ on_release:
+ if root.mode == 'nfc': root.mode = 'str'
+ root.mode = 'nfc'
+ GridLayout:
+ id: grid
+ cols: 1
+ size_hint: 1, None
+ height: self.minimum_height
+ SendReceiveCardTop
+ id: card_address
+ BoxLayout
+ size_hint: 1, None
+ height: '42dp'
+ rows: 1
+ Label
+ id: lbl_symbl
+ bold: True
+ color: amount_e.foreground_color
+ text_size: self.size
+ valign: 'bottom'
+ halign: 'left'
+ font_size: '22sp'
+ text:
+ u'[font={fnt}]{smbl}[/font]'.\
+ format(smbl=btc_symbol if app.base_unit == 'BTC' else mbtc_symbol, fnt=font_light)
+ size_hint_x: .25
+ ELTextInput:
+ id: amount_e
+ input_type: 'number'
+ multiline: False
+ bold: True
+ font_size: '50sp'
+ foreground_color: .308, .308, .308, 1
+ background_normal: 'atlas://gui/kivy/theming/light/tab_btn'
+ pos_hint: {'top': 1.5}
+ size_hint: .7, None
+ height: '67dp'
+ hint_text: 'Amount'
+ text: '0.0'
+ on_text_validate: payto_e.focus = True
+ CardSeparator
+ BoxLayout:
+ size_hint: 1, None
+ height: '42dp'
+ spacing: '5dp'
+ Label:
+ id: fee_e
+ color: .761, .761, .761, 1
+ font_size: '12dp'
+ amt: app.format_amount(app.wallet.fee)
+ text:
+ u'[b]{sign}{symbl}{amt}[/b] of fee'.\
+ format(symbl=lbl_symbl.text,\
+ sign='+' if self.amt > 0 else '-', amt=self.amt)
+ size_hint_x: None
+ width: self.texture_size[0]
+ halign: 'left'
+ valign: 'middle'
+ IconButton:
+ color: 0.694, 0.694, 0.694, 1
+ source: 'atlas://gui/kivy/theming/light/gear'
+ pos_hint: {'center_y': .5}
+ size_hint: None, None
+ size: '22dp', '22dp'
+ on_release:
+ dlg = Cache.get('electrum_widgets', 'TransactionFeeDialog')
+
+ if not dlg:\
+ Factory.register('SelectionDialog', module='electrum_gui.kivy.uix.dialogs');\
+ dlg = Factory.TransactionFeeDialog();\
+ Cache.append('electrum_widgets', 'TransactionDialog', dlg)
+
+ dlg.return_obj = fee_e
+ dlg.open()
+ Label:
+ font_size: '12dp'
+ color: fee_e.color
+ text: u'= {}'.format(app.create_quote_text(Decimal(float(amount_e.text)), mode='symbol')) if amount_e.text else u'0'
+ text_size: self.size
+ halign: 'right'
+ valign: 'middle'
+ SendReceiveBlueBottom:
+ id: blue_bottom
+ size_hint: 1, None
+ height: self.minimum_height
+ BoxLayout
+ size_hint: 1, None
+ height: blue_bottom.item_height
+ spacing: '5dp'
+ Image:
+ source: 'atlas://gui/kivy/theming/light/contact'
+ size_hint: None, None
+ size: '22dp', '22dp'
+ pos_hint: {'center_y': .5}
+ TextInputSendBlue:
+ id: payto_e
+ hint_text: "Enter Contact or adress"
+ on_text_validate:
+ Factory.Animation(opacity=1,\
+ height=blue_bottom.item_height)\
+ .start(message_selection)
+ message_e.focus = True
+ Widget:
+ size_hint: None, None
+ width: dp(2)
+ height: qr.height
+ pos_hint: {'center_y':.5}
+ canvas.after:
+ Rectangle:
+ size: self.size
+ pos: self.pos
+ IconButton:
+ id: qr
+ source: 'atlas://gui/kivy/theming/light/qrcode'
+ pos_hint: {'center_y': .5}
+ size_hint: None, None
+ size: '22dp', '22dp'
+ CardSeparator
+ opacity: message_selection.opacity
+ color: blue_bottom.foreground_color
+ BoxLayout:
+ id: message_selection
+ opacity: 1 if app.expert_mode else 0
+ size_hint: 1, None
+ height: blue_bottom.item_height if app.expert_mode else 0
+ spacing: '5dp'
+ Image:
+ source: 'atlas://gui/kivy/theming/light/pen'
+ size_hint: None, None
+ size: '22dp', '22dp'
+ pos_hint: {'center_y': .5}
+ TextInputSendBlue:
+ id: message_e
+ hint_text: 'Enter description here'
+ on_text_validate:
+ anim = Factory.Animation(opacity=1, height=blue_bottom.item_height)
+ anim.start(wallet_selection)
+ #anim.start(address_selection)
+ CardSeparator
+ opacity: wallet_selection.opacity
+ color: blue_bottom.foreground_color
+ WalletSelector:
+ id: wallet_selection
+ foreground_color: blue_bottom.foreground_color
+ opacity: 1 if app.expert_mode else 0
+ size_hint: 1, None
+ height: blue_bottom.item_height if app.expert_mode else 0
+ CardSeparator
+ opacity: address_selection.opacity
+ color: blue_bottom.foreground_color
+ AddressSelector:
+ id: address_selection
+ foreground_color: blue_bottom.foreground_color
+ opacity: 1 if app.expert_mode else 0
+ size_hint: 1, None
+ height: blue_bottom.item_height if app.expert_mode else 0
+ CreateAccountButtonGreen:
+ background_color: (1, 1, 1, 1) if self.disabled else ((.258, .80, .388, 1) if self.state == 'normal' else (.203, .490, .741, 1))
+ text: _('Goto next step') if app.wallet.seed else _('Create unsigned transaction')
+ size_hint_y: None
+ height: '38dp'
+ disabled: True if wallet_selection.opacity == 0 else False
+ on_release: app.do_send()
+ Widget
diff --git a/gui/kivy/utils.py b/gui/kivy/utils.py
@@ -1,2 +0,0 @@
-
-
diff --git a/lib/android/libiconv.so b/lib/android/libiconv.so
Binary files differ.
diff --git a/lib/android/libzbarjni.so b/lib/android/libzbarjni.so
Binary files differ.
diff --git a/lib/android/zbar.jar b/lib/android/zbar.jar
Binary files differ.