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