reorganize files and bring code inline with current master

Conflicts:
	lib/simple_config.py
This commit is contained in:
akshayaurora 2014-06-05 06:12:29 +05:30 committed by ThomasV
parent 9938316400
commit c121c1aa4e
67 changed files with 5360 additions and 2408 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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 \

View File

@ -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()

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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'
<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
<ListItemButton>
font_size: '12sp'
<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'
#########################
# Dialogs
#########################
################################################
## Create Dialogs
################################################
<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
StencilView:
manager: 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>
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
rgba: 1, 1, 1, 1
Rectangle
size: self.size
pos: self.pos

File diff suppressed because it is too large Load Diff

View File

@ -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': '', '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'',
'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)

View File

@ -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'])

View File

@ -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.

View File

@ -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()

View File

@ -1,7 +0,0 @@
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
class StatusBar(BoxLayout):
text = StringProperty('')

View File

@ -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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 475 KiB

After

Width:  |  Height:  |  Size: 476 KiB

View File

@ -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]}}
{"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]}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1022 B

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 561 B

After

Width:  |  Height:  |  Size: 866 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 481 B

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 838 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 887 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 B

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 B

After

Width:  |  Height:  |  Size: 260 B

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

1
gui/kivy/uix/__init__.py Normal file
View File

@ -0,0 +1 @@

View File

@ -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)

View File

@ -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]

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)
''')

257
gui/kivy/uix/drawer.py Normal file
View File

@ -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
'''))

View File

@ -90,11 +90,13 @@ class GridView(BoxLayout):
on_context_menu = ObjectProperty(None)
def __init__(self, **kwargs):
super(GridView, self).__init__(**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

View File

@ -16,6 +16,7 @@ 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'")
@ -36,11 +37,11 @@ Builder.load_string('''
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)
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}
@ -89,7 +90,7 @@ class QRCodeWidget(FloatLayout):
# 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.anim_delay = .05
img.source = self.loading_image
Thread(target=partial(self.generate_qr, value)).start()
@ -156,13 +157,13 @@ class QRCodeWidget(FloatLayout):
# then blit the buffer
buff = ''.join(map(chr, buff))
# update texture in UI thread.
Clock.schedule_once(lambda dt: self._upd_texture(buff))
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))
Clock.schedule_once(lambda dt: self._upd_texture(buff), .1)
return
texture.blit_buffer(buff, colorfmt='rgb', bufferfmt='ubyte')
img =self.ids.qrimage

300
gui/kivy/uix/screens.py Normal file
View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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

View File

@ -1,2 +0,0 @@

BIN
lib/android/libiconv.so Normal file

Binary file not shown.

BIN
lib/android/libzbarjni.so Normal file

Binary file not shown.

BIN
lib/android/zbar.jar Normal file

Binary file not shown.