reorganize files and bring code inline with current master
Conflicts: lib/simple_config.py
BIN
data/fonts/Roboto-Condensed.ttf
Normal file
BIN
data/fonts/Roboto-Medium.ttf
Normal file
|
@ -9,7 +9,7 @@ apk:
|
||||||
# running pre build setup
|
# running pre build setup
|
||||||
@cp tools/buildozer.spec ../../buildozer.spec
|
@cp tools/buildozer.spec ../../buildozer.spec
|
||||||
# get aes.py
|
# 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
|
# rename electrum to main.py
|
||||||
@mv ../../electrum ../../main.py
|
@mv ../../electrum ../../main.py
|
||||||
@-if [ ! -d "../../.buildozer" ];then \
|
@-if [ ! -d "../../.buildozer" ];then \
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
import sys
|
import sys
|
||||||
#, time, datetime, re, threading
|
#, time, datetime, re, threading
|
||||||
#from electrum.i18n import _, set_language
|
#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
|
#:TODO: replace this with kivy's own plugin managment
|
||||||
#from electrum.plugins import run_hook
|
#from electrum.plugins import run_hook
|
||||||
|
@ -42,9 +42,8 @@ from kivy.logger import Logger
|
||||||
|
|
||||||
from electrum.bitcoin import MIN_RELAY_TX_FEE
|
from electrum.bitcoin import MIN_RELAY_TX_FEE
|
||||||
|
|
||||||
#:TODO main window
|
|
||||||
from main_window import ElectrumWindow
|
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
|
#:TODO find a equivalent method to register to `bitcoin:` uri
|
||||||
#: ref: http://stackoverflow.com/questions/30931/register-file-extensions-mime-types-in-linux
|
#: 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 True
|
||||||
# return False
|
# return False
|
||||||
|
|
||||||
|
|
||||||
class ElectrumGui:
|
class ElectrumGui:
|
||||||
|
|
||||||
def __init__(self, config, network, app=None):
|
def __init__(self, config, network, app=None):
|
||||||
|
@ -74,6 +72,47 @@ class ElectrumGui:
|
||||||
# base
|
# base
|
||||||
#init_plugins(self)
|
#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):
|
def main(self, url):
|
||||||
''' The main entry point of the kivy ux
|
''' The main entry point of the kivy ux
|
||||||
|
@ -83,5 +122,7 @@ class ElectrumGui:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
self.main_window = w = ElectrumWindow(config=self.config,
|
self.main_window = w = ElectrumWindow(config=self.config,
|
||||||
network=self.network)
|
network=self.network,
|
||||||
w.run()
|
url=url,
|
||||||
|
gui_object=self)
|
||||||
|
w.run()
|
|
@ -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
|
|
|
@ -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")
|
|
|
@ -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
|
|
|
@ -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
|
|
435
gui/kivy/main.kv
|
@ -1,7 +1,4 @@
|
||||||
#:import Window kivy.core.window.Window
|
#:import Window kivy.core.window.Window
|
||||||
#:import _ electrum.i18n._
|
|
||||||
#:import partial functools.partial
|
|
||||||
|
|
||||||
|
|
||||||
# Custom Global Widgets
|
# Custom Global Widgets
|
||||||
|
|
||||||
|
@ -22,37 +19,36 @@
|
||||||
if root.state == 'normal' else 'icon_border')
|
if root.state == 'normal' else 'icon_border')
|
||||||
size: root.size
|
size: root.size
|
||||||
pos: root.pos
|
pos: root.pos
|
||||||
###########################
|
|
||||||
## Gloabal Defaults
|
|
||||||
###########################
|
|
||||||
|
|
||||||
<Label>
|
<Butt_star@ActionToggleButton>:
|
||||||
markup: True
|
important: True
|
||||||
font_name: 'Roboto'
|
size_hint_x: None
|
||||||
font_size: '16sp'
|
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>
|
<ELTextInput>
|
||||||
font_size: '12sp'
|
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>
|
<CreateAccountButtonBlue@Button>
|
||||||
canvas.after:
|
canvas.after:
|
||||||
|
@ -75,26 +71,40 @@
|
||||||
text_size: self.size
|
text_size: self.size
|
||||||
halign: 'center'
|
halign: 'center'
|
||||||
valign: 'middle'
|
valign: 'middle'
|
||||||
|
root: None
|
||||||
background_normal: 'atlas://gui/kivy/theming/light/btn_create_account'
|
background_normal: 'atlas://gui/kivy/theming/light/btn_create_account'
|
||||||
background_down: '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'
|
background_disabled_normal: 'atlas://gui/kivy/theming/light/btn_create_act_disabled'
|
||||||
on_release: self.root.dispatch('on_press', self)
|
on_press: if self.root: self.root.dispatch('on_press', self)
|
||||||
on_release: self.root.dispatch('on_release', self)
|
on_release: if self.root: self.root.dispatch('on_release', self)
|
||||||
|
|
||||||
|
|
||||||
<CreateAccountButtonGreen@CreateAccountButtonBlue>
|
<CreateAccountButtonGreen@CreateAccountButtonBlue>
|
||||||
background_color: (1, 1, 1, 1) if self.disabled else (.415, .717, 0, 1 if self.state == 'normal' else .75)
|
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>
|
<InfoBubble>
|
||||||
canvas.before:
|
|
||||||
Color:
|
|
||||||
rgba: 0, 0, 0, .7 if root.dim_background else 0
|
|
||||||
Rectangle:
|
|
||||||
size: Window.size
|
|
||||||
size_hint: None, None
|
size_hint: None, None
|
||||||
width: '270dp' if root.fs else min(self.width, dp(270))
|
width: '270dp' if root.fs else min(self.width, dp(270))
|
||||||
height: self.width if self.fs else (lbl.texture_size[1] + dp(27))
|
height: self.width if self.fs else (lbl.texture_size[1] + dp(27))
|
||||||
BoxLayout:
|
BoxLayout:
|
||||||
padding: '5dp'
|
padding: '5dp' if root.fs else 0
|
||||||
Widget:
|
Widget:
|
||||||
size_hint: None, 1
|
size_hint: None, 1
|
||||||
width: '4dp' if root.fs else '2dp'
|
width: '4dp' if root.fs else '2dp'
|
||||||
|
@ -117,346 +127,11 @@
|
||||||
size_hint: 1, 1
|
size_hint: 1, 1
|
||||||
width: 0 if root.fs else (root.width - img.width)
|
width: 0 if root.fs else (root.width - img.width)
|
||||||
|
|
||||||
<-CreateAccountDialog>
|
StencilView:
|
||||||
text_color: .854, .925, .984, 1
|
manager: None
|
||||||
auto_dismiss: False
|
|
||||||
size_hint: None, None
|
|
||||||
canvas.before:
|
canvas.before:
|
||||||
Color:
|
Color:
|
||||||
rgba: 0, 0, 0, .9
|
rgba: 1, 1, 1, 1
|
||||||
Rectangle:
|
Rectangle
|
||||||
size: Window.size
|
size: self.size
|
||||||
Color:
|
pos: self.pos
|
||||||
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
|
|
|
@ -6,13 +6,18 @@ This module is responsible for getting the conversion rates from different
|
||||||
bitcoin exchanges.
|
bitcoin exchanges.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
import decimal
|
||||||
|
import json
|
||||||
|
|
||||||
from kivy.network.urlrequest import UrlRequest
|
from kivy.network.urlrequest import UrlRequest
|
||||||
from kivy.event import EventDispatcher
|
from kivy.event import EventDispatcher
|
||||||
from kivy.properties import (OptionProperty, StringProperty, AliasProperty,
|
from kivy.properties import (OptionProperty, StringProperty, AliasProperty,
|
||||||
ListProperty)
|
ListProperty)
|
||||||
from kivy.clock import Clock
|
from kivy.clock import Clock
|
||||||
import decimal
|
from kivy.cache import Cache
|
||||||
import json
|
|
||||||
|
# Register local cache
|
||||||
|
Cache.register('history_rate', timeout=220)
|
||||||
|
|
||||||
EXCHANGES = ["BitcoinAverage",
|
EXCHANGES = ["BitcoinAverage",
|
||||||
"BitcoinVenezuela",
|
"BitcoinVenezuela",
|
||||||
|
@ -25,27 +30,32 @@ EXCHANGES = ["BitcoinAverage",
|
||||||
"LocalBitcoins",
|
"LocalBitcoins",
|
||||||
"Winkdex"]
|
"Winkdex"]
|
||||||
|
|
||||||
|
HISTORY_EXCHNAGES = ['Coindesk',
|
||||||
|
'Winkdex',
|
||||||
|
'BitcoinVenezuela']
|
||||||
|
|
||||||
|
|
||||||
class Exchanger(EventDispatcher):
|
class Exchanger(EventDispatcher):
|
||||||
''' Provide exchanges rate between crypto and different national
|
''' Provide exchanges rate between crypto and different national
|
||||||
currencies. See Module Documentation for details.
|
currencies. See Module Documentation for details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
symbols = {'ALL': 'Lek', 'AED': 'د.إ', 'AFN':'؋', 'ARS': '$', 'AMD': '֏',
|
symbols = {'ALL': u'Lek', 'AED': u'د.إ', 'AFN':u'؋', 'ARS': u'$',
|
||||||
'AWG': 'ƒ', 'ANG': 'ƒ', 'AOA': 'Kz', 'BDT': '৳', 'BHD': 'BD',
|
'AMD': u'֏', 'AWG': u'ƒ', 'ANG': u'ƒ', 'AOA': u'Kz', 'BDT': u'৳',
|
||||||
'BIF': 'FBu', 'BTC': 'BTC', 'BTN': 'Nu', 'CDF': 'FC', 'CHF': 'CHF',
|
'BHD': u'BD', 'BIF': u'FBu', 'BTC': u'BTC', 'BTN': u'Nu', 'CDF': u'FC',
|
||||||
'CLF': 'UF', 'CLP':'$', 'CVE': '$', 'DJF':'Fdj', 'DZD': 'دج',
|
'CHF': u'CHF', 'CLF': u'UF', 'CLP':u'$', 'CVE': u'$', 'DJF':u'Fdj',
|
||||||
'AUD': '$', 'AZN': 'ман', 'BSD': '$', 'BBD': '$', 'BYR': 'p', 'CRC': '₡',
|
'DZD': u'دج', 'AUD': u'$', 'AZN': u'ман', 'BSD': u'$', 'BBD': u'$',
|
||||||
'BZD': 'BZ$', 'BMD': '$', 'BOB': '$b', 'BAM': 'KM', 'BWP': 'P',
|
'BYR': u'p', 'CRC': u'₡', 'BZD': u'BZ$', 'BMD': u'$', 'BOB': u'$b',
|
||||||
'BGN': 'лв', 'BRL': 'R$', 'BND': '$', 'KHR': '៛', 'CAD': '$',
|
'BAM': u'KM', 'BWP': u'P', 'BGN': 'uлв', 'BRL': u'R$', 'BND': u'$',
|
||||||
'ERN': 'Nfk', 'ETB': 'Br', 'KYD': '$', 'USD': '$', 'CLP': '$',
|
'KHR': u'៛', 'CAD': u'$', 'ERN': u'Nfk', 'ETB': u'Br', 'KYD': u'$',
|
||||||
'HRK': 'kn', 'CUP':'₱', 'CZK': 'Kč', 'DKK': 'kr', 'DOP': 'RD$',
|
'USD': u'$', 'CLP': u'$', 'HRK': u'kn', 'CUP': u'₱', 'CZK': u'Kč',
|
||||||
'XCD': '$', 'EGP': '£', 'SVC': '$' , 'EEK': 'kr', 'EUR': '€',
|
'DKK': u'kr', 'DOP': u'RD$', 'XCD': u'$', 'EGP': u'£', 'SVC': u'$' ,
|
||||||
'FKP': '£', 'FJD': '$', 'GHC': '¢', 'GIP': '£', 'GTQ': 'Q', 'GBP': '£',
|
'EEK': u'kr', 'EUR': u'€', u'FKP': u'£', 'FJD': u'$', 'GHC': u'¢',
|
||||||
'GYD': '$', 'HNL': 'L', 'HKD': '$', 'HUF': 'Ft', 'ISK': 'kr',
|
'GIP': u'£', 'GTQ': u'Q', 'GBP': u'£', 'GYD': u'$', 'HNL': u'L',
|
||||||
'INR': '₹', 'IDR': 'Rp', 'IRR': '﷼', 'IMP': '£', 'ILS': '₪', 'COP': '$',
|
'HKD': u'$', 'HUF': u'Ft', 'ISK': u'kr', 'INR': u'₹', 'IDR': u'Rp',
|
||||||
'JMD': 'J$', 'JPY': '¥', 'JEP': '£', 'KZT': 'лв', 'KPW': '₩',
|
'IRR': u'﷼', 'IMP': '£', 'ILS': '₪', 'COP': '$', 'JMD': u'J$',
|
||||||
'KRW': '₩', 'KGS': 'лв', 'LAK': '₭', 'LVL': 'Ls', 'CNY': '¥'}
|
'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)
|
_use_exchange = OptionProperty('Blockchain', options=EXCHANGES)
|
||||||
'''This is the exchange to be used for getting the currency exchange rates
|
'''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):
|
def _set_currency(self, value):
|
||||||
exchanger = self.exchanger
|
value = str(value)
|
||||||
if self.use_exchange == 'CoinDesk':
|
if self.use_exchange == 'CoinDesk':
|
||||||
self._update_cd_currency(self.currency)
|
self._update_cd_currency(self.currency)
|
||||||
return
|
return
|
||||||
try:
|
self._currency = value
|
||||||
self._currency = value
|
self.parent.electrum_config.set_key('currency', value, True)
|
||||||
self.electrum_cinfig.set_key('currency', value, True)
|
|
||||||
except AttributeError:
|
|
||||||
self._currency = 'EUR'
|
|
||||||
|
|
||||||
def _get_currency(self):
|
def _get_currency(self):
|
||||||
try:
|
self._currency = self.parent.electrum_config.get('currency', 'EUR')
|
||||||
self._currency = self.electrum_config.get('currency', 'EUR')
|
return self._currency
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
return self._currency
|
|
||||||
|
|
||||||
currency = AliasProperty(_get_currency, _set_currency, bind=('_currency',))
|
currency = AliasProperty(_get_currency, _set_currency, bind=('_currency',))
|
||||||
|
|
||||||
|
@ -104,6 +107,7 @@ class Exchanger(EventDispatcher):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.quote_currencies = None
|
self.quote_currencies = None
|
||||||
self.exchanges = EXCHANGES
|
self.exchanges = EXCHANGES
|
||||||
|
self.history_exchanges = HISTORY_EXCHNAGES
|
||||||
|
|
||||||
def exchange(self, btc_amount, quote_currency):
|
def exchange(self, btc_amount, quote_currency):
|
||||||
if self.quote_currencies is None:
|
if self.quote_currencies is None:
|
||||||
|
@ -115,10 +119,40 @@ class Exchanger(EventDispatcher):
|
||||||
|
|
||||||
return btc_amount * decimal.Decimal(quote_currencies[quote_currency])
|
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):
|
def update_rate(self, dt):
|
||||||
''' This is called from :method:`start` every X seconds; to update the
|
''' This is called from :method:`start` every X seconds; to update the
|
||||||
rates for currencies for the currently selected exchange.
|
rates for currencies for the currently selected exchange.
|
||||||
'''
|
'''
|
||||||
|
if not self.parent.network or not self.parent.network.is_connected():
|
||||||
|
return
|
||||||
|
|
||||||
update_rates = {
|
update_rates = {
|
||||||
"BitcoinAverage": self.update_ba,
|
"BitcoinAverage": self.update_ba,
|
||||||
"BitcoinVenezuela": self.update_bv,
|
"BitcoinVenezuela": self.update_bv,
|
||||||
|
@ -268,7 +302,7 @@ class Exchanger(EventDispatcher):
|
||||||
for r in response:
|
for r in response:
|
||||||
quote_currencies[r] = _lookup_rate(response, r)
|
quote_currencies[r] = _lookup_rate(response, r)
|
||||||
self.quote_currencies = quote_currencies
|
self.quote_currencies = quote_currencies
|
||||||
except KeyError:
|
except KeyError, TypeError:
|
||||||
pass
|
pass
|
||||||
self.parent.set_currencies(quote_currencies)
|
self.parent.set_currencies(quote_currencies)
|
||||||
|
|
||||||
|
@ -329,9 +363,8 @@ class Exchanger(EventDispatcher):
|
||||||
timeout=5)
|
timeout=5)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
# check rates every few seconds
|
|
||||||
self.update_rate(0)
|
self.update_rate(0)
|
||||||
# check every few seconds
|
# check every 20 seconds
|
||||||
Clock.unschedule(self.update_rate)
|
Clock.unschedule(self.update_rate)
|
||||||
Clock.schedule_interval(self.update_rate, 20)
|
Clock.schedule_interval(self.update_rate, 20)
|
||||||
|
|
||||||
|
|
|
@ -7,69 +7,23 @@ from collections import namedtuple
|
||||||
|
|
||||||
from kivy.uix.anchorlayout import AnchorLayout
|
from kivy.uix.anchorlayout import AnchorLayout
|
||||||
from kivy.core import core_select_lib
|
from kivy.core import core_select_lib
|
||||||
|
from kivy.metrics import dp
|
||||||
from kivy.properties import ListProperty, BooleanProperty
|
from kivy.properties import ListProperty, BooleanProperty
|
||||||
from kivy.factory import Factory
|
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):
|
class ScannerBase(AnchorLayout):
|
||||||
''' Base implementation for camera based scanner
|
''' 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([])
|
symbols = ListProperty([])
|
||||||
|
|
||||||
# XXX can't work now, due to overlay.
|
# XXX can't work now, due to overlay.
|
||||||
show_bounds = BooleanProperty(False)
|
show_bounds = BooleanProperty(False)
|
||||||
|
|
||||||
|
running = BooleanProperty(False)
|
||||||
|
|
||||||
Qrcode = namedtuple('Qrcode',
|
Qrcode = namedtuple('Qrcode',
|
||||||
['type', 'data', 'bounds', 'quality', 'count'])
|
['type', 'data', 'bounds', 'quality', 'count'])
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ class SurfaceHolderCallback(PythonJavaClass):
|
||||||
def __init__(self, callback):
|
def __init__(self, callback):
|
||||||
super(SurfaceHolderCallback, self).__init__()
|
super(SurfaceHolderCallback, self).__init__()
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
|
|
||||||
@java_method('(Landroid/view/SurfaceHolder;III)V')
|
@java_method('(Landroid/view/SurfaceHolder;III)V')
|
||||||
def surfaceChanged(self, surface, fmt, width, height):
|
def surfaceChanged(self, surface, fmt, width, height):
|
||||||
self.callback(fmt, width, height)
|
self.callback(fmt, width, height)
|
||||||
|
@ -96,7 +96,7 @@ class SurfaceHolderCallback(PythonJavaClass):
|
||||||
@java_method('(Landroid/view/SurfaceHolder;)V')
|
@java_method('(Landroid/view/SurfaceHolder;)V')
|
||||||
def surfaceCreated(self, surface):
|
def surfaceCreated(self, surface):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@java_method('(Landroid/view/SurfaceHolder;)V')
|
@java_method('(Landroid/view/SurfaceHolder;)V')
|
||||||
def surfaceDestroyed(self, surface):
|
def surfaceDestroyed(self, surface):
|
||||||
pass
|
pass
|
||||||
|
@ -170,6 +170,7 @@ class AndroidCamera(Widget):
|
||||||
|
|
||||||
@run_on_ui_thread
|
@run_on_ui_thread
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
self.running = False
|
||||||
if self._android_camera is None:
|
if self._android_camera is None:
|
||||||
return
|
return
|
||||||
self._android_camera.setPreviewCallback(None)
|
self._android_camera.setPreviewCallback(None)
|
||||||
|
@ -179,6 +180,7 @@ class AndroidCamera(Widget):
|
||||||
|
|
||||||
@run_on_ui_thread
|
@run_on_ui_thread
|
||||||
def start(self):
|
def start(self):
|
||||||
|
self.running = True
|
||||||
if self._android_camera is not None:
|
if self._android_camera is not None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -196,6 +198,9 @@ class AndroidCamera(Widget):
|
||||||
# attach the android surfaceview to our android widget holder
|
# attach the android surfaceview to our android widget holder
|
||||||
self._holder.view = self._android_surface
|
self._holder.view = self._android_surface
|
||||||
|
|
||||||
|
# set orientation
|
||||||
|
self._android_camera.setDisplayOrientation(90)
|
||||||
|
|
||||||
def _on_surface_changed(self, fmt, width, height):
|
def _on_surface_changed(self, fmt, width, height):
|
||||||
# internal, called when the android SurfaceView is ready
|
# internal, called when the android SurfaceView is ready
|
||||||
# FIXME if the size is not handled by the camera, it will failed.
|
# FIXME if the size is not handled by the camera, it will failed.
|
||||||
|
|
|
@ -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()
|
|
|
@ -1,7 +0,0 @@
|
||||||
from kivy.uix.boxlayout import BoxLayout
|
|
||||||
from kivy.properties import StringProperty
|
|
||||||
|
|
||||||
|
|
||||||
class StatusBar(BoxLayout):
|
|
||||||
|
|
||||||
text = StringProperty('')
|
|
|
@ -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)
|
|
Before Width: | Height: | Size: 475 KiB After Width: | Height: | Size: 476 KiB |
|
@ -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]}}
|
Before Width: | Height: | Size: 1,022 B After Width: | Height: | Size: 552 B |
BIN
gui/kivy/theming/light/action_button_group.png
Normal file
After Width: | Height: | Size: 188 B |
BIN
gui/kivy/theming/light/action_group_light.png
Normal file
After Width: | Height: | Size: 375 B |
BIN
gui/kivy/theming/light/bit_logo.png
Normal file
After Width: | Height: | Size: 683 B |
Before Width: | Height: | Size: 561 B After Width: | Height: | Size: 866 B |
Before Width: | Height: | Size: 481 B After Width: | Height: | Size: 550 B |
Before Width: | Height: | Size: 838 B After Width: | Height: | Size: 1.2 KiB |
BIN
gui/kivy/theming/light/contact_avatar.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
gui/kivy/theming/light/contact_overlay.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
gui/kivy/theming/light/dropdown_background.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
gui/kivy/theming/light/overflow_background.png
Normal file
After Width: | Height: | Size: 887 B |
BIN
gui/kivy/theming/light/overflow_btn_dn.png
Normal file
After Width: | Height: | Size: 184 B |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 216 B |
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 222 B After Width: | Height: | Size: 260 B |
99
gui/kivy/tools/blacklist.txt
Normal 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
|
||||||
|
|
172
gui/kivy/tools/buildozer.spec
Normal 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
|
|
@ -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
|
|
129
gui/kivy/ui_screens/screenreceive.kv
Normal 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)
|
187
gui/kivy/ui_screens/screensend.kv
Normal 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
|
@ -0,0 +1 @@
|
||||||
|
|
190
gui/kivy/uix/dialogs/__init__.py
Normal 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)
|
239
gui/kivy/uix/dialogs/carousel_dialog.py
Normal 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]
|
488
gui/kivy/uix/dialogs/create_restore.py
Normal 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()
|
478
gui/kivy/uix/dialogs/installwizard.py
Normal 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
|
26
gui/kivy/uix/dialogs/new_contact.py
Normal 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
|
32
gui/kivy/uix/dialogs/nfc_transaction.py
Normal 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)
|
41
gui/kivy/uix/dialogs/qr_scanner.py
Normal 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
|
@ -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
|
||||||
|
'''))
|
|
@ -90,11 +90,13 @@ class GridView(BoxLayout):
|
||||||
on_context_menu = ObjectProperty(None)
|
on_context_menu = ObjectProperty(None)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(GridView, self).__init__(**kwargs)
|
|
||||||
self._from_widths = False
|
self._from_widths = False
|
||||||
|
super(GridView, self).__init__(**kwargs)
|
||||||
#self.on_headers(self, self.headers)
|
#self.on_headers(self, self.headers)
|
||||||
|
|
||||||
def on_widths(self, instance, value):
|
def on_widths(self, instance, value):
|
||||||
|
if not self.get_root_window():
|
||||||
|
return
|
||||||
self._from_widths = True
|
self._from_widths = True
|
||||||
self.on_headers(instance, self.headers)
|
self.on_headers(instance, self.headers)
|
||||||
self._from_widths = False
|
self._from_widths = False
|
|
@ -16,6 +16,7 @@ from kivy.clock import Clock
|
||||||
try:
|
try:
|
||||||
import qrcode
|
import qrcode
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
import sys
|
||||||
sys.exit("Error: qrcode does not seem to be installed. Try 'sudo pip install qrcode'")
|
sys.exit("Error: qrcode does not seem to be installed. Try 'sudo pip install qrcode'")
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,11 +37,11 @@ Builder.load_string('''
|
||||||
Line:
|
Line:
|
||||||
width: dp(1.333)
|
width: dp(1.333)
|
||||||
points:
|
points:
|
||||||
dp(2), dp(2),\
|
self.x + dp(2), self.y + dp(2),\
|
||||||
self.width - dp(2), dp(2),\
|
self.right - dp(2), self.y + dp(2),\
|
||||||
self.width - dp(2), self.height - dp(2),\
|
self.right - dp(2), self.top - dp(2),\
|
||||||
dp(2), self.height - dp(2),\
|
self.x + dp(2), self.top - dp(2),\
|
||||||
dp(2), dp(2)
|
self.x + dp(2), self.y + dp(2)
|
||||||
Image
|
Image
|
||||||
id: qrimage
|
id: qrimage
|
||||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
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
|
# if texture hasn't yet been created delay the texture updation
|
||||||
Clock.schedule_once(lambda dt: self.on_data(instance, value))
|
Clock.schedule_once(lambda dt: self.on_data(instance, value))
|
||||||
return
|
return
|
||||||
img.anim_delay = .25
|
img.anim_delay = .05
|
||||||
img.source = self.loading_image
|
img.source = self.loading_image
|
||||||
Thread(target=partial(self.generate_qr, value)).start()
|
Thread(target=partial(self.generate_qr, value)).start()
|
||||||
|
|
||||||
|
@ -156,13 +157,13 @@ class QRCodeWidget(FloatLayout):
|
||||||
# then blit the buffer
|
# then blit the buffer
|
||||||
buff = ''.join(map(chr, buff))
|
buff = ''.join(map(chr, buff))
|
||||||
# update texture in UI thread.
|
# 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):
|
def _upd_texture(self, buff):
|
||||||
texture = self._qrtexture
|
texture = self._qrtexture
|
||||||
if not texture:
|
if not texture:
|
||||||
# if texture hasn't yet been created delay the texture updation
|
# 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
|
return
|
||||||
texture.blit_buffer(buff, colorfmt='rgb', bufferfmt='ubyte')
|
texture.blit_buffer(buff, colorfmt='rgb', bufferfmt='ubyte')
|
||||||
img =self.ids.qrimage
|
img =self.ids.qrimage
|
300
gui/kivy/uix/screens.py
Normal 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)
|
1405
gui/kivy/uix/ui_screens/mainscreen.kv
Normal file
138
gui/kivy/uix/ui_screens/screenreceive.kv
Normal 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)
|
232
gui/kivy/uix/ui_screens/screensend.kv
Normal 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
|
|
@ -1,2 +0,0 @@
|
||||||
|
|
||||||
|
|