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