reorganize files and bring code inline with current master
Conflicts: lib/simple_config.py
|
@ -9,7 +9,7 @@ apk:
|
|||
# running pre build setup
|
||||
@cp tools/buildozer.spec ../../buildozer.spec
|
||||
# get aes.py
|
||||
@cd ../..; wget -4 https://raw.github.com/devrandom/slowaes/master/python/aes.py
|
||||
@cd ../..; curl -O https://raw.github.com/devrandom/slowaes/master/python/aes.py
|
||||
# rename electrum to main.py
|
||||
@mv ../../electrum ../../main.py
|
||||
@-if [ ! -d "../../.buildozer" ];then \
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
import sys
|
||||
#, time, datetime, re, threading
|
||||
#from electrum.i18n import _, set_language
|
||||
#from electrum.util import print_error, print_msg, parse_url
|
||||
from electrum.util import print_error, print_msg, parse_url
|
||||
|
||||
#:TODO: replace this with kivy's own plugin managment
|
||||
#from electrum.plugins import run_hook
|
||||
|
@ -42,9 +42,8 @@ from kivy.logger import Logger
|
|||
|
||||
from electrum.bitcoin import MIN_RELAY_TX_FEE
|
||||
|
||||
#:TODO main window
|
||||
from main_window import ElectrumWindow
|
||||
from electrum.plugins import init_plugins
|
||||
#from electrum.plugins import init_plugins
|
||||
|
||||
#:TODO find a equivalent method to register to `bitcoin:` uri
|
||||
#: ref: http://stackoverflow.com/questions/30931/register-file-extensions-mime-types-in-linux
|
||||
|
@ -60,7 +59,6 @@ from electrum.plugins import init_plugins
|
|||
# return True
|
||||
# return False
|
||||
|
||||
|
||||
class ElectrumGui:
|
||||
|
||||
def __init__(self, config, network, app=None):
|
||||
|
@ -74,6 +72,47 @@ class ElectrumGui:
|
|||
# base
|
||||
#init_plugins(self)
|
||||
|
||||
def set_url(self, url):
|
||||
from electrum import util
|
||||
from decimal import Decimal
|
||||
|
||||
try:
|
||||
address, amount, label, message,\
|
||||
request_url, url = util.parse_url(url)
|
||||
except Exception:
|
||||
self.main_window.show_error(_('Invalid bitcoin URL'))
|
||||
return
|
||||
|
||||
if amount:
|
||||
try:
|
||||
if main_window.base_unit == 'mBTC':
|
||||
amount = str( 1000* Decimal(amount))
|
||||
else:
|
||||
amount = str(Decimal(amount))
|
||||
except Exception:
|
||||
amount = "0.0"
|
||||
self.main_window.show_error(_('Invalid Amount'))
|
||||
|
||||
if request_url:
|
||||
try:
|
||||
from electrum import paymentrequest
|
||||
except:
|
||||
self.main_window.show_error("cannot import payment request")
|
||||
request_url = None
|
||||
|
||||
if not request_url:
|
||||
self.main_window.set_send(address, amount, label, message)
|
||||
return
|
||||
|
||||
def payment_request():
|
||||
self.payment_request = paymentrequest.PaymentRequest(request_url)
|
||||
if self.payment_request.verify():
|
||||
Clock.schedule_once(self.main_window.payment_request_ok)
|
||||
else:
|
||||
Clock.schedule_once(self.main_window.payment_request_error)
|
||||
|
||||
threading.Thread(target=payment_request).start()
|
||||
self.main_window.prepare_for_payment_request()
|
||||
|
||||
def main(self, url):
|
||||
''' The main entry point of the kivy ux
|
||||
|
@ -83,5 +122,7 @@ class ElectrumGui:
|
|||
'''
|
||||
|
||||
self.main_window = w = ElectrumWindow(config=self.config,
|
||||
network=self.network)
|
||||
w.run()
|
||||
network=self.network,
|
||||
url=url,
|
||||
gui_object=self)
|
||||
w.run()
|
|
@ -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 _ electrum.i18n._
|
||||
#:import partial functools.partial
|
||||
|
||||
|
||||
# Custom Global Widgets
|
||||
|
||||
|
@ -22,37 +19,36 @@
|
|||
if root.state == 'normal' else 'icon_border')
|
||||
size: root.size
|
||||
pos: root.pos
|
||||
###########################
|
||||
## Gloabal Defaults
|
||||
###########################
|
||||
|
||||
<Label>
|
||||
markup: True
|
||||
font_name: 'Roboto'
|
||||
font_size: '16sp'
|
||||
<Butt_star@ActionToggleButton>:
|
||||
important: True
|
||||
size_hint_x: None
|
||||
width: '32dp'
|
||||
mipmap: True
|
||||
state: 'down' if app.expert_mode else 'normal'
|
||||
background_down: self.background_normal
|
||||
foreground_color: (.466, .466, .466, 1)
|
||||
color_active: (0.235, .588, .89, 1)
|
||||
on_release: app.expert_mode = True if self.state == 'down' else False
|
||||
Image:
|
||||
source: 'atlas://gui/kivy/theming/light/star_big_inactive'
|
||||
center: root.center
|
||||
size: root.width/1.5, self.width
|
||||
color:
|
||||
root.foreground_color if root.state == 'normal' else root.color_active
|
||||
canvas.after:
|
||||
Color:
|
||||
rgba: 1, 1, 1, 1
|
||||
source:
|
||||
allow_stretch: True
|
||||
|
||||
<ListItemButton>
|
||||
font_size: '12sp'
|
||||
<ELTextInput>
|
||||
padding: '10dp', '4dp'
|
||||
background_color: (0.238, 0.589, .996, 1) if self.focus else self.foreground_color
|
||||
foreground_color: 0.531, 0.531, 0.531, 1
|
||||
background_active: 'atlas://gui/kivy/theming/light/textinput_active'
|
||||
background_normal: 'atlas://gui/kivy/theming/light/textinput_active'
|
||||
|
||||
#########################
|
||||
# Dialogs
|
||||
#########################
|
||||
|
||||
################################################
|
||||
## Create Dialogs
|
||||
################################################
|
||||
|
||||
<CreateAccountTextInput@TextInput>
|
||||
border: 4, 4, 4, 4
|
||||
font_size: '15sp'
|
||||
padding: '15dp', '15dp'
|
||||
background_color: (1, 1, 1, 1) if self.focus else (0.454, 0.698, 0.909, 1)
|
||||
foreground_color: (0.31, 0.31, 0.31, 1) if self.focus else (0.835, 0.909, 0.972, 1)
|
||||
hint_text_color: self.foreground_color
|
||||
background_active: 'atlas://gui/kivy/theming/light/create_act_text_active'
|
||||
background_normal: 'atlas://gui/kivy/theming/light/create_act_text_active'
|
||||
size_hint_y: None
|
||||
height: '48sp'
|
||||
|
||||
<CreateAccountButtonBlue@Button>
|
||||
canvas.after:
|
||||
|
@ -75,26 +71,40 @@
|
|||
text_size: self.size
|
||||
halign: 'center'
|
||||
valign: 'middle'
|
||||
root: None
|
||||
background_normal: 'atlas://gui/kivy/theming/light/btn_create_account'
|
||||
background_down: 'atlas://gui/kivy/theming/light/btn_create_account'
|
||||
background_disabled_normal: 'atlas://gui/kivy/theming/light/btn_create_act_disabled'
|
||||
on_release: self.root.dispatch('on_press', self)
|
||||
on_release: self.root.dispatch('on_release', self)
|
||||
on_press: if self.root: self.root.dispatch('on_press', self)
|
||||
on_release: if self.root: self.root.dispatch('on_release', self)
|
||||
|
||||
|
||||
<CreateAccountButtonGreen@CreateAccountButtonBlue>
|
||||
background_color: (1, 1, 1, 1) if self.disabled else (.415, .717, 0, 1 if self.state == 'normal' else .75)
|
||||
###########################
|
||||
## Gloabal Defaults
|
||||
###########################
|
||||
<TextInput>
|
||||
on_focus: app._focused_widget = root
|
||||
|
||||
<Label>
|
||||
markup: True
|
||||
font_name: 'Roboto'
|
||||
font_size: '16sp'
|
||||
|
||||
<ListItemButton>
|
||||
font_size: '12sp'
|
||||
|
||||
#########################
|
||||
# Dialogs
|
||||
#########################
|
||||
|
||||
<InfoBubble>
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: 0, 0, 0, .7 if root.dim_background else 0
|
||||
Rectangle:
|
||||
size: Window.size
|
||||
size_hint: None, None
|
||||
width: '270dp' if root.fs else min(self.width, dp(270))
|
||||
height: self.width if self.fs else (lbl.texture_size[1] + dp(27))
|
||||
BoxLayout:
|
||||
padding: '5dp'
|
||||
padding: '5dp' if root.fs else 0
|
||||
Widget:
|
||||
size_hint: None, 1
|
||||
width: '4dp' if root.fs else '2dp'
|
||||
|
@ -117,346 +127,11 @@
|
|||
size_hint: 1, 1
|
||||
width: 0 if root.fs else (root.width - img.width)
|
||||
|
||||
<-CreateAccountDialog>
|
||||
text_color: .854, .925, .984, 1
|
||||
auto_dismiss: False
|
||||
size_hint: None, None
|
||||
StencilView:
|
||||
manager: None
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: 0, 0, 0, .9
|
||||
Rectangle:
|
||||
size: Window.size
|
||||
Color:
|
||||
rgba: .239, .588, .882, 1
|
||||
Rectangle:
|
||||
size: Window.size
|
||||
|
||||
crcontent: crcontent
|
||||
# add electrum icon
|
||||
FloatLayout:
|
||||
size_hint: None, None
|
||||
size: 0, 0
|
||||
IconButton:
|
||||
id: but_close
|
||||
size_hint: None, None
|
||||
size: '27dp', '27dp'
|
||||
top: Window.height - dp(10)
|
||||
right: Window.width - dp(10)
|
||||
source: 'atlas://gui/kivy/theming/light/closebutton'
|
||||
on_release: root.dispatch('on_press', self)
|
||||
on_release: root.dispatch('on_release', self)
|
||||
BoxLayout:
|
||||
orientation: 'vertical' if self.width < self.height else 'horizontal'
|
||||
padding:
|
||||
min(dp(42), self.width/8), min(dp(60), self.height/9.7),\
|
||||
min(dp(42), self.width/8), min(dp(72), self.height/8)
|
||||
spacing: '27dp'
|
||||
GridLayout:
|
||||
id: grid_logo
|
||||
cols: 1
|
||||
pos_hint: {'center_y': .5}
|
||||
size_hint: 1, .62
|
||||
#height: self.minimum_height
|
||||
Image:
|
||||
id: logo_img
|
||||
mipmap: True
|
||||
allow_stretch: True
|
||||
size_hint: 1, None
|
||||
height: '110dp'
|
||||
source: 'atlas://gui/kivy/theming/light/electrum_icon640'
|
||||
Widget:
|
||||
size_hint: 1, None
|
||||
height: 0 if stepper.opacity else dp(15)
|
||||
Label:
|
||||
color: root.text_color
|
||||
opacity: 0 if stepper.opacity else 1
|
||||
text: 'ELECTRUM'
|
||||
size_hint: 1, None
|
||||
height: self.texture_size[1] if self.opacity else 0
|
||||
font_size: '33sp'
|
||||
font_name: 'data/fonts/tron/Tr2n.ttf'
|
||||
Image:
|
||||
id: stepper
|
||||
allow_stretch: True
|
||||
opacity: 0
|
||||
source: 'atlas://gui/kivy/theming/light/stepper_left'
|
||||
size_hint: 1, None
|
||||
height: grid_logo.height/2.5 if self.opacity else 0
|
||||
Widget:
|
||||
size_hint: None, None
|
||||
size: '5dp', '5dp'
|
||||
GridLayout:
|
||||
cols: 1
|
||||
id: crcontent
|
||||
spacing: '13dp'
|
||||
|
||||
<CreateRestoreDialog>
|
||||
Label:
|
||||
color: root.text_color
|
||||
size_hint: 1, None
|
||||
text_size: self.width, None
|
||||
height: self.texture_size[1]
|
||||
text:
|
||||
_("Wallet file not found!!")+\
|
||||
"\n\n" + _("Do you want to create a new wallet ")+\
|
||||
_("or restore an existing one?")
|
||||
Widget
|
||||
size_hint: 1, None
|
||||
height: dp(15)
|
||||
GridLayout:
|
||||
id: grid
|
||||
orientation: 'vertical'
|
||||
cols: 1
|
||||
spacing: '14dp'
|
||||
size_hint: 1, None
|
||||
height: self.minimum_height
|
||||
CreateAccountButtonGreen:
|
||||
id: create
|
||||
text: _('Create a Wallet')
|
||||
root: root
|
||||
CreateAccountButtonBlue:
|
||||
id: restore
|
||||
text: _('I already have a wallet')
|
||||
root: root
|
||||
#CreateAccountButtonBlue:
|
||||
# id: watching
|
||||
# text: _('Create a Watching only wallet')
|
||||
# root: root
|
||||
|
||||
<RestoreSeedDialog>
|
||||
GridLayout
|
||||
# leave room for future selection of gap through a widget
|
||||
# removed for mobile
|
||||
id: text_input_gap
|
||||
text: '5'
|
||||
|
||||
cols: 1
|
||||
padding: 0, '12dp'
|
||||
orientation: 'vertical'
|
||||
spacing: '12dp'
|
||||
size_hint: 1, None
|
||||
height: self.minimum_height
|
||||
CreateAccountTextInput:
|
||||
id: text_input_seed
|
||||
size_hint: 1, None
|
||||
height: '110dp'
|
||||
hint_text:
|
||||
_('Enter your seedphrase')
|
||||
Label:
|
||||
font_size: '12sp'
|
||||
text_size: self.width, None
|
||||
size_hint: 1, None
|
||||
height: self.texture_size[1]
|
||||
halign: 'justify'
|
||||
valign: 'middle'
|
||||
text:
|
||||
_('If you need additional information, please check '
|
||||
'[color=#0000ff][ref=1]'
|
||||
'https://electrum.org/faq.html#seed[/ref][/color]')
|
||||
on_ref_press:
|
||||
import webbrowser
|
||||
webbrowser.open('https://electrum.org/faq.html#seed')
|
||||
GridLayout:
|
||||
rows: 1
|
||||
spacing: '12dp'
|
||||
size_hint: 1, None
|
||||
height: self.minimum_height
|
||||
CreateAccountButtonBlue:
|
||||
id: back
|
||||
text: _('Back')
|
||||
root: root
|
||||
CreateAccountButtonGreen:
|
||||
id: next
|
||||
text: _('Next')
|
||||
root: root
|
||||
|
||||
<InitSeedDialog>
|
||||
spacing: '12dp'
|
||||
GridLayout:
|
||||
id: grid
|
||||
cols: 1
|
||||
pos_hint: {'center_y': .5}
|
||||
size_hint_y: None
|
||||
height: dp(180)
|
||||
orientation: 'vertical'
|
||||
Button:
|
||||
border: 4, 4, 4, 4
|
||||
halign: 'justify'
|
||||
valign: 'middle'
|
||||
font_size: self.width/21
|
||||
text_size: self.width - dp(24), self.height - dp(12)
|
||||
#size_hint: 1, None
|
||||
#height: self.texture_size[1] + dp(24)
|
||||
background_normal: 'atlas://gui/kivy/theming/light/white_bg_round_top'
|
||||
background_down: self.background_normal
|
||||
text: root.message
|
||||
GridLayout:
|
||||
rows: 1
|
||||
size_hint: 1, .7
|
||||
#size_hint_y: None
|
||||
#height: but_seed.texture_size[1] + dp(24)
|
||||
Button:
|
||||
id: but_seed
|
||||
border: 4, 4, 4, 4
|
||||
halign: 'justify'
|
||||
valign: 'middle'
|
||||
font_size: self.width/15
|
||||
text: root.seed_msg
|
||||
text_size: self.width - dp(24), self.height - dp(12)
|
||||
background_normal: 'atlas://gui/kivy/theming/light/lightblue_bg_round_lb'
|
||||
background_down: self.background_normal
|
||||
Button:
|
||||
id: bt
|
||||
size_hint_x: .25
|
||||
background_normal: 'atlas://gui/kivy/theming/light/blue_bg_round_rb'
|
||||
background_down: self.background_normal
|
||||
Image:
|
||||
mipmap: True
|
||||
source: 'atlas://gui/kivy/theming/light/qrcode'
|
||||
size: bt.size
|
||||
center: bt.center
|
||||
#on_release:
|
||||
GridLayout:
|
||||
rows: 1
|
||||
spacing: '12dp'
|
||||
size_hint: 1, None
|
||||
height: self.minimum_height
|
||||
CreateAccountButtonBlue:
|
||||
id: back
|
||||
text: _('Back')
|
||||
root: root
|
||||
CreateAccountButtonGreen:
|
||||
id: confirm
|
||||
text: _('Confirm')
|
||||
root: root
|
||||
|
||||
<ChangePasswordDialog>
|
||||
padding: '7dp'
|
||||
GridLayout:
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
cols: 1
|
||||
CreateAccountTextInput:
|
||||
id: ti_wallet_name
|
||||
hint_text: 'Your Wallet Name'
|
||||
multiline: False
|
||||
on_text_validate:
|
||||
next = ti_new_password if ti_password.disabled else ti_password
|
||||
next.focus = True
|
||||
Widget:
|
||||
size_hint_y: None
|
||||
height: '13dp'
|
||||
CreateAccountTextInput:
|
||||
id: ti_password
|
||||
hint_text: 'Enter old pincode'
|
||||
size_hint_y: None
|
||||
height: 0 if self.disabled else '38sp'
|
||||
password: True
|
||||
disabled: True if root.mode in ('new', 'create', 'restore') else False
|
||||
opacity: 0 if self.disabled else 1
|
||||
multiline: False
|
||||
on_text_validate:
|
||||
#root.validate_old_password()
|
||||
ti_new_password.focus = True
|
||||
Widget:
|
||||
size_hint_y: None
|
||||
height: 0 if ti_password.disabled else '13dp'
|
||||
CreateAccountTextInput:
|
||||
id: ti_new_password
|
||||
hint_text: 'Enter new pincode'
|
||||
multiline: False
|
||||
password: True
|
||||
on_text_validate: ti_confirm_password.focus = True
|
||||
Widget:
|
||||
size_hint_y: None
|
||||
height: '13dp'
|
||||
CreateAccountTextInput:
|
||||
id: ti_confirm_password
|
||||
hint_text: 'Confirm pincode'
|
||||
password: True
|
||||
multiline: False
|
||||
on_text_validate: root.validate_new_password()
|
||||
Widget
|
||||
GridLayout:
|
||||
rows: 1
|
||||
spacing: '12dp'
|
||||
size_hint: 1, None
|
||||
height: self.minimum_height
|
||||
CreateAccountButtonBlue:
|
||||
id: back
|
||||
text: _('Back')
|
||||
root: root
|
||||
disabled: True if root.mode[0] == 'r' else self.disabled
|
||||
CreateAccountButtonGreen:
|
||||
id: next
|
||||
text: _('Confirm') if root.mode[0] == 'r' else _('Next')
|
||||
root: root
|
||||
|
||||
###############################################
|
||||
## Wallet Management
|
||||
###############################################
|
||||
|
||||
<WalletManagement@ScrollView>
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: .145, .145, .145, 1
|
||||
Rectangle:
|
||||
size: root.size
|
||||
pos: root.pos
|
||||
VGridLayout:
|
||||
Wallets:
|
||||
id: wallets_section
|
||||
Plugins:
|
||||
id: plugins_section
|
||||
Commands:
|
||||
id: commands_section
|
||||
|
||||
<WalletManagementItem@BoxLayout>
|
||||
|
||||
<Header@WalletManagementItem>
|
||||
|
||||
<Wallets@VGridLayout>
|
||||
Header
|
||||
|
||||
<Plugins@VGridLayout>
|
||||
Header
|
||||
|
||||
<Commands@VGridLayout>
|
||||
Header
|
||||
|
||||
################################################
|
||||
## This is our Root Widget of the app
|
||||
################################################
|
||||
StencilView
|
||||
manager: manager
|
||||
Drawer
|
||||
id: drawer
|
||||
size: root.size
|
||||
WalletManagement
|
||||
id: wallet_management
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: .176, .176, .176, 1
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
width:
|
||||
(root.width * .877) if app.ui_mode[0] == 'p'\
|
||||
else root.width * .35 if app.orientation[0] == 'l'\
|
||||
else root.width * .10
|
||||
height: root.height
|
||||
BoxLayout:
|
||||
x: wallet_management.width if app.ui_mode[0] == 't' else 0
|
||||
width: (root.width - self.x) if app.ui_mode[0] == 't' else root.width
|
||||
size_hint: None, None
|
||||
height: root.height
|
||||
canvas.before:
|
||||
Color
|
||||
rgba: 1, 1, 1, 1
|
||||
BorderImage
|
||||
border: 0, 32, 0, 0
|
||||
source: 'atlas://gui/kivy/theming/light/shadow_right'
|
||||
pos: root.pos
|
||||
size: self.x, self.height
|
||||
ScreenManager:
|
||||
id: manager
|
||||
rgba: 1, 1, 1, 1
|
||||
Rectangle
|
||||
size: self.size
|
||||
pos: self.pos
|
|
@ -6,13 +6,18 @@ This module is responsible for getting the conversion rates from different
|
|||
bitcoin exchanges.
|
||||
'''
|
||||
|
||||
import decimal
|
||||
import json
|
||||
|
||||
from kivy.network.urlrequest import UrlRequest
|
||||
from kivy.event import EventDispatcher
|
||||
from kivy.properties import (OptionProperty, StringProperty, AliasProperty,
|
||||
ListProperty)
|
||||
from kivy.clock import Clock
|
||||
import decimal
|
||||
import json
|
||||
from kivy.cache import Cache
|
||||
|
||||
# Register local cache
|
||||
Cache.register('history_rate', timeout=220)
|
||||
|
||||
EXCHANGES = ["BitcoinAverage",
|
||||
"BitcoinVenezuela",
|
||||
|
@ -25,27 +30,32 @@ EXCHANGES = ["BitcoinAverage",
|
|||
"LocalBitcoins",
|
||||
"Winkdex"]
|
||||
|
||||
HISTORY_EXCHNAGES = ['Coindesk',
|
||||
'Winkdex',
|
||||
'BitcoinVenezuela']
|
||||
|
||||
|
||||
class Exchanger(EventDispatcher):
|
||||
''' Provide exchanges rate between crypto and different national
|
||||
currencies. See Module Documentation for details.
|
||||
'''
|
||||
|
||||
symbols = {'ALL': 'Lek', 'AED': 'د.إ', 'AFN':'؋', 'ARS': '$', 'AMD': '֏',
|
||||
'AWG': 'ƒ', 'ANG': 'ƒ', 'AOA': 'Kz', 'BDT': '৳', 'BHD': 'BD',
|
||||
'BIF': 'FBu', 'BTC': 'BTC', 'BTN': 'Nu', 'CDF': 'FC', 'CHF': 'CHF',
|
||||
'CLF': 'UF', 'CLP':'$', 'CVE': '$', 'DJF':'Fdj', 'DZD': 'دج',
|
||||
'AUD': '$', 'AZN': 'ман', 'BSD': '$', 'BBD': '$', 'BYR': 'p', 'CRC': '₡',
|
||||
'BZD': 'BZ$', 'BMD': '$', 'BOB': '$b', 'BAM': 'KM', 'BWP': 'P',
|
||||
'BGN': 'лв', 'BRL': 'R$', 'BND': '$', 'KHR': '៛', 'CAD': '$',
|
||||
'ERN': 'Nfk', 'ETB': 'Br', 'KYD': '$', 'USD': '$', 'CLP': '$',
|
||||
'HRK': 'kn', 'CUP':'₱', 'CZK': 'Kč', 'DKK': 'kr', 'DOP': 'RD$',
|
||||
'XCD': '$', 'EGP': '£', 'SVC': '$' , 'EEK': 'kr', 'EUR': '€',
|
||||
'FKP': '£', 'FJD': '$', 'GHC': '¢', 'GIP': '£', 'GTQ': 'Q', 'GBP': '£',
|
||||
'GYD': '$', 'HNL': 'L', 'HKD': '$', 'HUF': 'Ft', 'ISK': 'kr',
|
||||
'INR': '₹', 'IDR': 'Rp', 'IRR': '﷼', 'IMP': '£', 'ILS': '₪', 'COP': '$',
|
||||
'JMD': 'J$', 'JPY': '¥', 'JEP': '£', 'KZT': 'лв', 'KPW': '₩',
|
||||
'KRW': '₩', 'KGS': 'лв', 'LAK': '₭', 'LVL': 'Ls', 'CNY': '¥'}
|
||||
symbols = {'ALL': u'Lek', 'AED': u'د.إ', 'AFN':u'؋', 'ARS': u'$',
|
||||
'AMD': u'֏', 'AWG': u'ƒ', 'ANG': u'ƒ', 'AOA': u'Kz', 'BDT': u'৳',
|
||||
'BHD': u'BD', 'BIF': u'FBu', 'BTC': u'BTC', 'BTN': u'Nu', 'CDF': u'FC',
|
||||
'CHF': u'CHF', 'CLF': u'UF', 'CLP':u'$', 'CVE': u'$', 'DJF':u'Fdj',
|
||||
'DZD': u'دج', 'AUD': u'$', 'AZN': u'ман', 'BSD': u'$', 'BBD': u'$',
|
||||
'BYR': u'p', 'CRC': u'₡', 'BZD': u'BZ$', 'BMD': u'$', 'BOB': u'$b',
|
||||
'BAM': u'KM', 'BWP': u'P', 'BGN': 'uлв', 'BRL': u'R$', 'BND': u'$',
|
||||
'KHR': u'៛', 'CAD': u'$', 'ERN': u'Nfk', 'ETB': u'Br', 'KYD': u'$',
|
||||
'USD': u'$', 'CLP': u'$', 'HRK': u'kn', 'CUP': u'₱', 'CZK': u'Kč',
|
||||
'DKK': u'kr', 'DOP': u'RD$', 'XCD': u'$', 'EGP': u'£', 'SVC': u'$' ,
|
||||
'EEK': u'kr', 'EUR': u'€', u'FKP': u'£', 'FJD': u'$', 'GHC': u'¢',
|
||||
'GIP': u'£', 'GTQ': u'Q', 'GBP': u'£', 'GYD': u'$', 'HNL': u'L',
|
||||
'HKD': u'$', 'HUF': u'Ft', 'ISK': u'kr', 'INR': u'₹', 'IDR': u'Rp',
|
||||
'IRR': u'﷼', 'IMP': '£', 'ILS': '₪', 'COP': '$', 'JMD': u'J$',
|
||||
'JPY': u'¥', 'JEP': u'£', 'KZT': u'лв', 'KPW': u'₩', 'KRW': u'₩',
|
||||
'KGS': u'лв', 'LAK': u'₭', 'LVL': u'Ls', 'CNY': u'¥'}
|
||||
|
||||
_use_exchange = OptionProperty('Blockchain', options=EXCHANGES)
|
||||
'''This is the exchange to be used for getting the currency exchange rates
|
||||
|
@ -56,23 +66,16 @@ class Exchanger(EventDispatcher):
|
|||
'''
|
||||
|
||||
def _set_currency(self, value):
|
||||
exchanger = self.exchanger
|
||||
value = str(value)
|
||||
if self.use_exchange == 'CoinDesk':
|
||||
self._update_cd_currency(self.currency)
|
||||
return
|
||||
try:
|
||||
self._currency = value
|
||||
self.electrum_cinfig.set_key('currency', value, True)
|
||||
except AttributeError:
|
||||
self._currency = 'EUR'
|
||||
self._currency = value
|
||||
self.parent.electrum_config.set_key('currency', value, True)
|
||||
|
||||
def _get_currency(self):
|
||||
try:
|
||||
self._currency = self.electrum_config.get('currency', 'EUR')
|
||||
except AttributeError:
|
||||
pass
|
||||
finally:
|
||||
return self._currency
|
||||
self._currency = self.parent.electrum_config.get('currency', 'EUR')
|
||||
return self._currency
|
||||
|
||||
currency = AliasProperty(_get_currency, _set_currency, bind=('_currency',))
|
||||
|
||||
|
@ -104,6 +107,7 @@ class Exchanger(EventDispatcher):
|
|||
self.parent = parent
|
||||
self.quote_currencies = None
|
||||
self.exchanges = EXCHANGES
|
||||
self.history_exchanges = HISTORY_EXCHNAGES
|
||||
|
||||
def exchange(self, btc_amount, quote_currency):
|
||||
if self.quote_currencies is None:
|
||||
|
@ -115,10 +119,40 @@ class Exchanger(EventDispatcher):
|
|||
|
||||
return btc_amount * decimal.Decimal(quote_currencies[quote_currency])
|
||||
|
||||
def get_history_rate(self, item, btc_amt, mintime, maxtime):
|
||||
def on_success(request, response):
|
||||
response = json.loads(response)
|
||||
|
||||
try:
|
||||
hrate = response['bpi'][mintime]
|
||||
hrate = abs(btc_amt) * decimal.Decimal(hrate)
|
||||
Cache.append('history_rate', uid, hrate)
|
||||
except KeyError:
|
||||
hrate = 'not found'
|
||||
|
||||
self.parent.set_history_rate(item, hrate)
|
||||
|
||||
# Check local cache before getting data from remote
|
||||
exchange = 'coindesk'
|
||||
uid = '{}:{}'.format(exchange, mintime)
|
||||
hrate = Cache.get('history_rate', uid)
|
||||
|
||||
if hrate:
|
||||
return hrate
|
||||
|
||||
req = UrlRequest(url='https://api.coindesk.com/v1/bpi/historical'
|
||||
'/close.json?start={}&end={}'
|
||||
.format(mintime, maxtime)
|
||||
,on_success=on_success, timeout=15)
|
||||
return None
|
||||
|
||||
def update_rate(self, dt):
|
||||
''' This is called from :method:`start` every X seconds; to update the
|
||||
rates for currencies for the currently selected exchange.
|
||||
'''
|
||||
if not self.parent.network or not self.parent.network.is_connected():
|
||||
return
|
||||
|
||||
update_rates = {
|
||||
"BitcoinAverage": self.update_ba,
|
||||
"BitcoinVenezuela": self.update_bv,
|
||||
|
@ -268,7 +302,7 @@ class Exchanger(EventDispatcher):
|
|||
for r in response:
|
||||
quote_currencies[r] = _lookup_rate(response, r)
|
||||
self.quote_currencies = quote_currencies
|
||||
except KeyError:
|
||||
except KeyError, TypeError:
|
||||
pass
|
||||
self.parent.set_currencies(quote_currencies)
|
||||
|
||||
|
@ -329,9 +363,8 @@ class Exchanger(EventDispatcher):
|
|||
timeout=5)
|
||||
|
||||
def start(self):
|
||||
# check rates every few seconds
|
||||
self.update_rate(0)
|
||||
# check every few seconds
|
||||
# check every 20 seconds
|
||||
Clock.unschedule(self.update_rate)
|
||||
Clock.schedule_interval(self.update_rate, 20)
|
||||
|
||||
|
|
|
@ -7,69 +7,23 @@ from collections import namedtuple
|
|||
|
||||
from kivy.uix.anchorlayout import AnchorLayout
|
||||
from kivy.core import core_select_lib
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import ListProperty, BooleanProperty
|
||||
from kivy.factory import Factory
|
||||
|
||||
|
||||
def encode_uri(addr, amount=0, label='', message='', size='',
|
||||
currency='btc'):
|
||||
''' Convert to BIP0021 compatible URI
|
||||
'''
|
||||
uri = 'bitcoin:{}'.format(addr)
|
||||
first = True
|
||||
if amount:
|
||||
uri += '{}amount={}'.format('?' if first else '&', amount)
|
||||
first = False
|
||||
if label:
|
||||
uri += '{}label={}'.format('?' if first else '&', label)
|
||||
first = False
|
||||
if message:
|
||||
uri += '{}?message={}'.format('?' if first else '&', message)
|
||||
first = False
|
||||
if size:
|
||||
uri += '{}size={}'.format('?' if not first else '&', size)
|
||||
return uri
|
||||
|
||||
def decode_uri(uri):
|
||||
if ':' not in uri:
|
||||
# It's just an address (not BIP21)
|
||||
return {'address': uri}
|
||||
|
||||
if '//' not in uri:
|
||||
# Workaround for urlparse, it don't handle bitcoin: URI properly
|
||||
uri = uri.replace(':', '://')
|
||||
|
||||
try:
|
||||
uri = urlparse(uri)
|
||||
except NameError:
|
||||
# delayed import
|
||||
from urlparse import urlparse, parse_qs
|
||||
uri = urlparse(uri)
|
||||
|
||||
result = {'address': uri.netloc}
|
||||
|
||||
if uri.path.startswith('?'):
|
||||
params = parse_qs(uri.path[1:])
|
||||
else:
|
||||
params = parse_qs(uri.path)
|
||||
|
||||
for k,v in params.items():
|
||||
if k in ('amount', 'label', 'message', 'size'):
|
||||
result[k] = v[0]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class ScannerBase(AnchorLayout):
|
||||
''' Base implementation for camera based scanner
|
||||
'''
|
||||
camera_size = ListProperty([320, 240])
|
||||
camera_size = ListProperty([320, 240] if dp(1) < 2 else [640, 480])
|
||||
|
||||
symbols = ListProperty([])
|
||||
|
||||
# XXX can't work now, due to overlay.
|
||||
show_bounds = BooleanProperty(False)
|
||||
|
||||
running = BooleanProperty(False)
|
||||
|
||||
Qrcode = namedtuple('Qrcode',
|
||||
['type', 'data', 'bounds', 'quality', 'count'])
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ class SurfaceHolderCallback(PythonJavaClass):
|
|||
def __init__(self, callback):
|
||||
super(SurfaceHolderCallback, self).__init__()
|
||||
self.callback = callback
|
||||
|
||||
|
||||
@java_method('(Landroid/view/SurfaceHolder;III)V')
|
||||
def surfaceChanged(self, surface, fmt, width, height):
|
||||
self.callback(fmt, width, height)
|
||||
|
@ -96,7 +96,7 @@ class SurfaceHolderCallback(PythonJavaClass):
|
|||
@java_method('(Landroid/view/SurfaceHolder;)V')
|
||||
def surfaceCreated(self, surface):
|
||||
pass
|
||||
|
||||
|
||||
@java_method('(Landroid/view/SurfaceHolder;)V')
|
||||
def surfaceDestroyed(self, surface):
|
||||
pass
|
||||
|
@ -170,6 +170,7 @@ class AndroidCamera(Widget):
|
|||
|
||||
@run_on_ui_thread
|
||||
def stop(self):
|
||||
self.running = False
|
||||
if self._android_camera is None:
|
||||
return
|
||||
self._android_camera.setPreviewCallback(None)
|
||||
|
@ -179,6 +180,7 @@ class AndroidCamera(Widget):
|
|||
|
||||
@run_on_ui_thread
|
||||
def start(self):
|
||||
self.running = True
|
||||
if self._android_camera is not None:
|
||||
return
|
||||
|
||||
|
@ -196,6 +198,9 @@ class AndroidCamera(Widget):
|
|||
# attach the android surfaceview to our android widget holder
|
||||
self._holder.view = self._android_surface
|
||||
|
||||
# set orientation
|
||||
self._android_camera.setDisplayOrientation(90)
|
||||
|
||||
def _on_surface_changed(self, fmt, width, height):
|
||||
# internal, called when the android SurfaceView is ready
|
||||
# FIXME if the size is not handled by the camera, it will failed.
|
||||
|
|
|
@ -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: 1022 B After Width: | Height: | Size: 552 B |
After Width: | Height: | Size: 188 B |
After Width: | Height: | Size: 375 B |
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 |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 2.3 KiB |
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 |
After Width: | Height: | Size: 887 B |
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.0 KiB After Width: | Height: | Size: 3.0 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 |
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -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)
|
|
@ -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]
|
|
@ -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()
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
||||
''')
|
|
@ -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)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(GridView, self).__init__(**kwargs)
|
||||
self._from_widths = False
|
||||
super(GridView, self).__init__(**kwargs)
|
||||
#self.on_headers(self, self.headers)
|
||||
|
||||
def on_widths(self, instance, value):
|
||||
if not self.get_root_window():
|
||||
return
|
||||
self._from_widths = True
|
||||
self.on_headers(instance, self.headers)
|
||||
self._from_widths = False
|
|
@ -16,6 +16,7 @@ from kivy.clock import Clock
|
|||
try:
|
||||
import qrcode
|
||||
except ImportError:
|
||||
import sys
|
||||
sys.exit("Error: qrcode does not seem to be installed. Try 'sudo pip install qrcode'")
|
||||
|
||||
|
||||
|
@ -36,11 +37,11 @@ Builder.load_string('''
|
|||
Line:
|
||||
width: dp(1.333)
|
||||
points:
|
||||
dp(2), dp(2),\
|
||||
self.width - dp(2), dp(2),\
|
||||
self.width - dp(2), self.height - dp(2),\
|
||||
dp(2), self.height - dp(2),\
|
||||
dp(2), dp(2)
|
||||
self.x + dp(2), self.y + dp(2),\
|
||||
self.right - dp(2), self.y + dp(2),\
|
||||
self.right - dp(2), self.top - dp(2),\
|
||||
self.x + dp(2), self.top - dp(2),\
|
||||
self.x + dp(2), self.y + dp(2)
|
||||
Image
|
||||
id: qrimage
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
|
@ -89,7 +90,7 @@ class QRCodeWidget(FloatLayout):
|
|||
# if texture hasn't yet been created delay the texture updation
|
||||
Clock.schedule_once(lambda dt: self.on_data(instance, value))
|
||||
return
|
||||
img.anim_delay = .25
|
||||
img.anim_delay = .05
|
||||
img.source = self.loading_image
|
||||
Thread(target=partial(self.generate_qr, value)).start()
|
||||
|
||||
|
@ -156,13 +157,13 @@ class QRCodeWidget(FloatLayout):
|
|||
# then blit the buffer
|
||||
buff = ''.join(map(chr, buff))
|
||||
# update texture in UI thread.
|
||||
Clock.schedule_once(lambda dt: self._upd_texture(buff))
|
||||
Clock.schedule_once(lambda dt: self._upd_texture(buff), .1)
|
||||
|
||||
def _upd_texture(self, buff):
|
||||
texture = self._qrtexture
|
||||
if not texture:
|
||||
# if texture hasn't yet been created delay the texture updation
|
||||
Clock.schedule_once(lambda dt: self._upd_texture(buff))
|
||||
Clock.schedule_once(lambda dt: self._upd_texture(buff), .1)
|
||||
return
|
||||
texture.blit_buffer(buff, colorfmt='rgb', bufferfmt='ubyte')
|
||||
img =self.ids.qrimage
|
|
@ -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)
|
|
@ -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)
|
|
@ -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 @@
|
|||
|
||||
|