add gui/kivy changes from electrum 3.1.3

- Add electrum 3.1.3 changes to gui/kivy
- Get new electrum 3.1.3 gui/kivy/Readme.md
- Get new electrum 3.1.3 gui/kivy/uix/dialogs/requests.py.
- Get new electrum 3.1.3 gui/kivy/uix/dialogs/invoices.py.
- Get new electrum 3.1.3 gui/kivy/uix/dialogs/addresses.py
This commit is contained in:
zebra-lucky 2018-06-05 15:07:49 +03:00
parent 4df22e559c
commit 59e3058de2
30 changed files with 1030 additions and 667 deletions

80
gui/kivy/Readme.md Normal file
View File

@ -0,0 +1,80 @@
# Kivy GUI
The Kivy GUI is used with Electrum-Zcash on Android devices. To generate an APK file, follow these instructions.
## 1. Install python-for-android (p4a)
p4a is used to package Electrum-Zcash, Python, SDL and a bootstrap Java app into an APK file.
We patched p4a to add some functionality we need for Electrum-Zcash. Until those changes are
merged into p4a, you need to merge them locally (into the master branch):
1.1 [kivy/python-for-android#1217](https://github.com/kivy/python-for-android/pull/1217)
Something like this should work:
```sh
cd /opt
git clone https://github.com/kivy/python-for-android
cd python-for-android
git remote add agilewalker https://github.com/agilewalker/python-for-android
git fetch --all
git checkout 93759f36ba45c7bbe0456a4b3e6788622924cbac
git merge a2fb5ecbc09c4847adbcfd03c6b1ca62b3d09b8d
```
## 2. Install buildozer
2.1 Buildozer is a frontend to p4a. Luckily we don't need to patch it:
```sh
cd /opt
git clone https://github.com/kivy/buildozer
cd buildozer
sudo python3 setup.py install
```
2.2 Download the [Crystax NDK](https://www.crystax.net/en/download) manually.
Extract into `/opt/crystax-ndk-10.3.2`
## 3. Update the Android SDK build tools
### Method 1: Using the GUI
Start the Android SDK manager in GUI mode:
~/.buildozer/android/platform/android-sdk-20/tools/android
Check the latest SDK available and install it.
Close the SDK manager.
Reopen the SDK manager, scroll to the bottom and install the latest build tools (probably v27)
Install "Android Support Library Repository" from the SDK manager.
### Method 2: Using the command line:
Repeat the following command until there is nothing to install:
~/.buildozer/android/platform/android-sdk-20/tools/android update sdk -u -t tools,platform-tools
Install Build Tools, android API 19 and Android Support Library:
~/.buildozer/android/platform/android-sdk-20/tools/android update sdk -u -t build-tools-27.0.3,android-19,extra-android-m2repository
## 5. Create the UI Atlas
In the `gui/kivy` directory of Electrum-Zcash, run `make theming`.
## 6. Download Electrum-Zcash dependencies
Run `contrib/make_packages`.
## 7. Build the APK
Run `contrib/make_apk`.
# FAQ
## Why do I get errors like `package me.dm7.barcodescanner.zxing does not exist` while compiling?
Update your Android build tools to version 27 like described above.
## Why do I get errors like `(use -source 7 or higher to enable multi-catch statement)` while compiling?
Make sure that your p4a installation includes commit a3cc78a6d1a107cd3b6bd28db8b80f89e3ecddd2.
Also make sure you have recent SDK tools and platform-tools
## I changed something but I don't see any differences on the phone. What did I do wrong?
You probably need to clear the cache: `rm -rf .buildozer/android/platform/build/{build,dists}`

View File

@ -1,24 +0,0 @@
Before compiling, create packages: `contrib/make_packages`
Commands::
`make theming` to make a atlas out of a list of pngs
`make apk` to make a apk
If something in included modules like kivy or any other module changes
then you need to rebuild the distribution. To do so:
rm -rf .buildozer/android/platform/python-for-android/dist
how to build with ssl:
rm -rf .buildozer/android/platform/build/
./contrib/make_apk
pushd /opt/electrum/.buildozer/android/platform/build/build/libs_collections/Electrum/armeabi-v7a
cp libssl1.0.2g.so /opt/crystax-ndk-10.3.2/sources/openssl/1.0.2g/libs/armeabi-v7a/libssl.so
cp libcrypto1.0.2g.so /opt/crystax-ndk-10.3.2/sources/openssl/1.0.2g/libs/armeabi-v7a/libcrypto.so
popd
./contrib/make_apk

View File

@ -32,7 +32,7 @@ try:
sys.argv = ['']
import kivy
except ImportError:
# This error ideally shouldn't raised with pre-built packages
# This error ideally shouldn't be raised with pre-built packages
sys.exit("Error: Could not import kivy. Please install it using the" + \
"instructions mentioned here `http://kivy.org/#download` .")

View File

@ -1,21 +1,22 @@
import gettext
class _(str):
observers = set()
lang = None
def __new__(cls, s, *args, **kwargs):
def __new__(cls, s):
if _.lang is None:
_.switch_lang('en')
t = _.translate(s, *args, **kwargs)
t = _.translate(s)
o = super(_, cls).__new__(cls, t)
o.source_text = s
return o
@staticmethod
def translate(s, *args, **kwargs):
return _.lang(s).format(args, kwargs)
return _.lang(s)
@staticmethod
def bind(label):

View File

@ -239,7 +239,7 @@
self.screen.show_menu(args[0]) if self.state == 'down' else self.screen.hide_menu()
canvas.before:
Color:
rgba: (0.192, .498, 0.745, 1) if self.state == 'down' else (0.3, 0.3, 0.3, 1)
rgba: (0.192, .498, 0.745, 1) if self.state == 'down' else (0.15, 0.15, 0.17, 1)
Rectangle:
size: self.size
pos: self.pos
@ -285,7 +285,8 @@
<KButton@Button>:
size_hint: 1, None
height: '48dp'
height: '60dp'
font_size: '30dp'
on_release:
self.parent.update_amount(self.text)
@ -372,9 +373,6 @@
tab_height: '48dp'
tab_width: panel.width/3
strip_border: 0, 0, 0, 0
InvoicesScreen:
id: invoices_screen
tab: invoices_tab
SendScreen:
id: send_screen
tab: send_tab
@ -384,34 +382,23 @@
ReceiveScreen:
id: receive_screen
tab: receive_tab
AddressScreen:
id: address_screen
tab: address_tab
CleanHeader:
id: invoices_tab
text: _('Invoices')
slide: 0
CleanHeader:
id: send_tab
text: _('Send')
slide: 1
slide: 0
CleanHeader:
id: history_tab
text: _('History')
slide: 2
text: _('Balance')
slide: 1
CleanHeader:
id: receive_tab
text: _('Receive')
slide: 3
CleanHeader:
id: address_tab
text: _('Addresses')
slide: 4
slide: 2
<ActionOvrButton@ActionButton>
#on_release:
# fixme: the following line was commented out because it does no seem to do what it is intended
# fixme: the following line was commented out because it does not seem to do what it is intended
# Clock.schedule_once(lambda dt: self.parent.parent.dismiss() if self.parent else None, 0.05)
on_press:
Clock.schedule_once(lambda dt: app.popup_dialog(self.name), 0.05)

View File

@ -82,6 +82,10 @@ class ElectrumWindow(App):
server_port = StringProperty('')
num_chains = NumericProperty(0)
blockchain_name = StringProperty('')
fee_status = StringProperty('Fee')
balance = StringProperty('')
fiat_balance = StringProperty('')
is_fiat = BooleanProperty(False)
blockchain_checkpoint = NumericProperty(0)
auto_connect = BooleanProperty(False)
@ -95,8 +99,8 @@ class ElectrumWindow(App):
from .uix.dialogs.choice_dialog import ChoiceDialog
protocol = 's'
def cb2(host):
from electrum_zcash.bitcoin import NetworkConstants
pp = servers.get(host, NetworkConstants.DEFAULT_PORTS)
from electrum_zcash import constants
pp = servers.get(host, constants.net.DEFAULT_PORTS)
port = pp.get(protocol, '')
popup.ids.host.text = host
popup.ids.port.text = port
@ -171,8 +175,10 @@ class ElectrumWindow(App):
def btc_to_fiat(self, amount_str):
if not amount_str:
return ''
if not self.fx.is_enabled():
return ''
rate = self.fx.exchange_rate()
if not rate:
if rate.is_nan():
return ''
fiat_amount = self.get_amount(amount_str + ' ' + self.base_unit) * rate / pow(10, 8)
return "{:.2f}".format(fiat_amount).rstrip('0').rstrip('.')
@ -181,7 +187,7 @@ class ElectrumWindow(App):
if not fiat_amount:
return ''
rate = self.fx.exchange_rate()
if not rate:
if rate.is_nan():
return ''
satoshis = int(pow(10,8) * Decimal(fiat_amount) / Decimal(rate))
return format_satoshis_plain(satoshis, self.decimal_point())
@ -234,6 +240,7 @@ class ElectrumWindow(App):
self.tabs = None
self.is_exit = False
self.wallet = None
self.pause_time = 0
App.__init__(self)#, **kwargs)
@ -258,7 +265,7 @@ class ElectrumWindow(App):
self.use_change = config.get('use_change', True)
self.use_unconfirmed = not config.get('confirmed_only', False)
# create triggers so as to minimize updation a max of 2 times a sec
# create triggers so as to minimize updating a max of 2 times a sec
self._trigger_update_wallet = Clock.create_trigger(self.update_wallet, .5)
self._trigger_update_status = Clock.create_trigger(self.update_status, .5)
self._trigger_update_history = Clock.create_trigger(self.update_history, .5)
@ -266,6 +273,7 @@ class ElectrumWindow(App):
# cached dialogs
self._settings_dialog = None
self._password_dialog = None
self.fee_status = self.electrum_config.get_fee_status()
def wallet_name(self):
return os.path.basename(self.wallet.storage.path) if self.wallet else ' '
@ -387,12 +395,15 @@ class ElectrumWindow(App):
intent = Intent(PythonActivity.mActivity, SimpleScannerActivity)
def on_qr_result(requestCode, resultCode, intent):
if resultCode == -1: # RESULT_OK:
# this doesn't work due to some bug in jnius:
# contents = intent.getStringExtra("text")
String = autoclass("java.lang.String")
contents = intent.getStringExtra(String("text"))
on_complete(contents)
try:
if resultCode == -1: # RESULT_OK:
# this doesn't work due to some bug in jnius:
# contents = intent.getStringExtra("text")
String = autoclass("java.lang.String")
contents = intent.getStringExtra(String("text"))
on_complete(contents)
finally:
activity.unbind(on_activity_result=on_qr_result)
activity.bind(on_activity_result=on_qr_result)
PythonActivity.mActivity.startActivityForResult(intent, 0)
@ -433,7 +444,6 @@ class ElectrumWindow(App):
#win.softinput_mode = 'below_target'
self.on_size(win, win.size)
self.init_ui()
self.load_wallet_by_name(self.electrum_config.get_wallet_path())
# init plugins
run_hook('init_kivy', self)
# fiat currency
@ -452,8 +462,11 @@ class ElectrumWindow(App):
if self.network:
interests = ['updated', 'status', 'new_transaction', 'verified', 'interfaces']
self.network.register_callback(self.on_network_event, interests)
self.network.register_callback(self.on_fee, ['fee'])
self.network.register_callback(self.on_quotes, ['on_quotes'])
self.network.register_callback(self.on_history, ['on_history'])
# load wallet
self.load_wallet_by_name(self.electrum_config.get_wallet_path())
# URI passed in config
uri = self.electrum_config.get('url')
if uri:
@ -471,17 +484,18 @@ class ElectrumWindow(App):
wallet.start_threads(self.daemon.network)
self.daemon.add_wallet(wallet)
self.load_wallet(wallet)
self.on_resume()
def load_wallet_by_name(self, path):
if not path:
return
if self.wallet and self.wallet.storage.path == path:
return
wallet = self.daemon.load_wallet(path, None)
if wallet:
if wallet != self.wallet:
self.stop_wallet()
if wallet.has_password():
self.password_dialog(wallet, _('Enter PIN code'), lambda x: self.load_wallet(wallet), self.stop)
else:
self.load_wallet(wallet)
self.on_resume()
else:
Logger.debug('Electrum-Zcash: Wallet not found. Launching install wizard')
storage = WalletStorage(path)
@ -491,6 +505,7 @@ class ElectrumWindow(App):
wizard.run(action)
def on_stop(self):
Logger.info('on_stop')
self.stop_wallet()
def stop_wallet(self):
@ -604,6 +619,8 @@ class ElectrumWindow(App):
@profiler
def load_wallet(self, wallet):
if self.wallet:
self.stop_wallet()
self.wallet = wallet
self.update_wallet()
# Once GUI has been initialized check if we want to announce something
@ -626,20 +643,22 @@ class ElectrumWindow(App):
if not self.wallet.up_to_date or server_height == 0:
status = _("Synchronizing...")
elif server_lag > 1:
status = _("Server lagging (%d blocks)"%server_lag)
status = _("Server lagging")
else:
c, u, x = self.wallet.get_balance()
text = self.format_amount(c+x+u)
status = str(text.strip() + ' ' + self.base_unit)
status = ''
else:
status = _("Disconnected")
n = self.wallet.basename()
self.status = '[size=15dp]%s[/size]\n%s' %(n, status)
#fiat_balance = self.fx.format_amount_and_units(c+u+x) or ''
self.status = self.wallet.basename() + (' [size=15dp](%s)[/size]'%status if status else '')
# balance
c, u, x = self.wallet.get_balance()
text = self.format_amount(c+x+u)
self.balance = str(text.strip()) + ' [size=22dp]%s[/size]'% self.base_unit
self.fiat_balance = self.fx.format_amount(c+u+x) + ' [size=22dp]%s[/size]'% self.fx.ccy
def get_max_amount(self):
inputs = self.wallet.get_spendable_coins(None, self.electrum_config)
if not inputs:
return ''
addr = str(self.send_screen.screen.address) or self.wallet.dummy_address()
outputs = [(TYPE_ADDRESS, addr, '!')]
tx = self.wallet.make_unsigned_transaction(inputs, outputs, self.electrum_config)
@ -671,17 +690,18 @@ class ElectrumWindow(App):
Logger.Error('Notification: needs plyer; `sudo pip install plyer`')
def on_pause(self):
self.pause_time = time.time()
# pause nfc
if self.nfcscanner:
self.nfcscanner.nfc_disable()
return True
def on_resume(self):
now = time.time()
if self.wallet.has_password and now - self.pause_time > 60:
self.password_dialog(self.wallet, _('Enter PIN'), None, self.stop)
if self.nfcscanner:
self.nfcscanner.nfc_enable()
# workaround p4a bug:
# show an empty info bubble, to refresh the display
self.show_info_bubble('', duration=0.1, pos=(0,0), width=1, arrow_pos=None)
def on_size(self, instance, value):
width, height = value
@ -703,7 +723,7 @@ class ElectrumWindow(App):
def show_error(self, error, width='200dp', pos=None, arrow_pos=None,
exit=False, icon='atlas://gui/kivy/theming/light/error', duration=0,
modal=False):
''' Show a error Message Bubble.
''' Show an error Message Bubble.
'''
self.show_info_bubble( text=error, icon=icon, width=width,
pos=pos or Window.center, arrow_pos=arrow_pos, exit=exit,
@ -711,7 +731,7 @@ class ElectrumWindow(App):
def show_info(self, error, width='200dp', pos=None, arrow_pos=None,
exit=False, duration=0, modal=False):
''' Show a Info Message Bubble.
''' Show an Info Message Bubble.
'''
self.show_error(error, icon='atlas://gui/kivy/theming/light/important',
duration=duration, modal=modal, exit=exit, pos=pos,
@ -719,7 +739,7 @@ class ElectrumWindow(App):
def show_info_bubble(self, text=_('Hello World'), pos=None, duration=0,
arrow_pos='bottom_mid', width=None, icon='', modal=False, exit=False):
'''Method to show a Information Bubble
'''Method to show an Information Bubble
.. parameters::
text: Message to be displayed
@ -814,7 +834,6 @@ class ElectrumWindow(App):
d = LabelDialog(_('Enter description'), text, callback)
d.open()
@profiler
def amount_dialog(self, screen, show_max):
from .uix.dialogs.amount_dialog import AmountDialog
amount = screen.amount
@ -826,9 +845,48 @@ class ElectrumWindow(App):
popup = AmountDialog(show_max, amount, cb)
popup.open()
def invoices_dialog(self, screen):
from .uix.dialogs.invoices import InvoicesDialog
if len(self.wallet.invoices.sorted_list()) == 0:
self.show_info(' '.join([
_('No saved invoices.'),
_('Signed invoices are saved automatically when you scan them.'),
_('You may also save unsigned requests or contact addresses using the save button.')
]))
return
popup = InvoicesDialog(self, screen, None)
popup.update()
popup.open()
def requests_dialog(self, screen):
from .uix.dialogs.requests import RequestsDialog
if len(self.wallet.get_sorted_requests(self.electrum_config)) == 0:
self.show_info(_('No saved requests.'))
return
popup = RequestsDialog(self, screen, None)
popup.update()
popup.open()
def addresses_dialog(self, screen):
from .uix.dialogs.addresses import AddressesDialog
popup = AddressesDialog(self, screen, None)
popup.update()
popup.open()
def fee_dialog(self, label, dt):
from .uix.dialogs.fee_dialog import FeeDialog
def cb():
self.fee_status = self.electrum_config.get_fee_status()
fee_dialog = FeeDialog(self, self.electrum_config, cb)
fee_dialog.open()
def on_fee(self, event, *arg):
self.fee_status = self.electrum_config.get_fee_status()
def protected(self, msg, f, args):
if self.wallet.has_password():
self.password_dialog(msg, f, args)
on_success = lambda pw: f(*(args + (pw,)))
self.password_dialog(self.wallet, msg, on_success, lambda: None)
else:
f(*(args + (None,)))
@ -840,8 +898,8 @@ class ElectrumWindow(App):
def _delete_wallet(self, b):
if b:
basename = os.path.basename(self.wallet.storage.path)
self.protected(_("Enter your PIN code to confirm deletion of %s") % basename, self.__delete_wallet, ())
basename = self.wallet.basename()
self.protected(_("Enter your PIN code to confirm deletion of {}").format(basename), self.__delete_wallet, ())
def __delete_wallet(self, pw):
wallet_path = self.get_wallet_path()
@ -878,40 +936,23 @@ class ElectrumWindow(App):
if passphrase:
label.text += '\n\n' + _('Passphrase') + ': ' + passphrase
def change_password(self, cb):
if self.wallet.has_password():
self.protected(_("Changing PIN code.") + '\n' + _("Enter your current PIN:"), self._change_password, (cb,))
else:
self._change_password(cb, None)
def _change_password(self, cb, old_password):
if self.wallet.has_password():
if old_password is None:
return
try:
self.wallet.check_password(old_password)
except InvalidPassword:
self.show_error("Invalid PIN")
return
self.password_dialog(_('Enter new PIN'), self._change_password2, (cb, old_password,))
def _change_password2(self, cb, old_password, new_password):
self.password_dialog(_('Confirm new PIN'), self._change_password3, (cb, old_password, new_password))
def _change_password3(self, cb, old_password, new_password, confirmed_password):
if new_password == confirmed_password:
self.wallet.update_password(old_password, new_password)
cb()
else:
self.show_error("PIN numbers do not match")
def password_dialog(self, msg, f, args):
def password_dialog(self, wallet, msg, on_success, on_failure):
from .uix.dialogs.password_dialog import PasswordDialog
def callback(pw):
Clock.schedule_once(lambda x: f(*(args + (pw,))), 0.1)
if self._password_dialog is None:
self._password_dialog = PasswordDialog()
self._password_dialog.init(msg, callback)
self._password_dialog.init(self, wallet, msg, on_success, on_failure)
self._password_dialog.open()
def change_password(self, cb):
from .uix.dialogs.password_dialog import PasswordDialog
if self._password_dialog is None:
self._password_dialog = PasswordDialog()
message = _("Changing PIN code.") + '\n' + _("Enter your current PIN:")
def on_success(old_password, new_password):
self.wallet.update_password(old_password, new_password)
self.show_info(_("Your PIN code was updated"))
on_failure = lambda: self.show_error(_("PIN codes do not match"))
self._password_dialog.init(self, self.wallet, message, on_success, on_failure, is_change=1)
self._password_dialog.open()
def export_private_keys(self, pk_label, addr):

View File

@ -3,7 +3,7 @@ __all__ = ('NFCBase', 'NFCScanner')
class NFCBase(Widget):
''' This is the base Abstract definition class that the actual hardware dependent
implementations would be based on. If you want to define a feature that is
accissible and implemented by every platform implementation then define that
accessible and implemented by every platform implementation then define that
method in this class.
'''
@ -39,6 +39,6 @@ class NFCBase(Widget):
# load NFCScanner implementation
NFCScanner = core_select_lib('nfc_manager', (
# keep the dummy implementtation as the last one to make it the fallback provider.NFCScanner = core_select_lib('nfc_scanner', (
# keep the dummy implementation as the last one to make it the fallback provider.NFCScanner = core_select_lib('nfc_scanner', (
('android', 'scanner_android', 'ScannerAndroid'),
('dummy', 'scanner_dummy', 'ScannerDummy')), True, 'electrum_zcash_gui.kivy')

View File

@ -1,4 +1,4 @@
'''This is the Android implementatoin of NFC Scanning using the
'''This is the Android implementation of NFC Scanning using the
built in NFC adapter of some android phones.
'''
@ -33,8 +33,8 @@ app = None
class ScannerAndroid(NFCBase):
''' This is the class responsible for handling the interace with the
Android NFC adapter. See Module Documentation for deatils.
''' This is the class responsible for handling the interface with the
Android NFC adapter. See Module Documentation for details.
'''
name = 'NFCAndroid'
@ -56,7 +56,7 @@ class ScannerAndroid(NFCBase):
if not self.nfc_adapter:
return False
# specify that we want our activity to remain on top whan a new intent
# specify that we want our activity to remain on top when a new intent
# is fired
self.nfc_pending_intent = PendingIntent.getActivity(context, 0,
Intent(context, context.getClass()).addFlags(
@ -128,7 +128,7 @@ class ScannerAndroid(NFCBase):
return details
def on_new_intent(self, intent):
''' This functions is called when the application receives a
''' This function is called when the application receives a
new intent, for the ones the application has registered previously,
either in the manifest or in the foreground dispatch setup in the
nfc_init function above.
@ -184,7 +184,7 @@ class ScannerAndroid(NFCBase):
return extRecord
def create_ndef_message(self, *recs):
''' Create the Ndef message that will written to tag
''' Create the Ndef message that will be written to tag
'''
records = []
for record in recs:

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -107,6 +107,9 @@ android.add_activities = org.electrum.qr.SimpleScannerActivity
# (str) XML file to include as an intent filters in <activity> tag
android.manifest.intent_filters = gui/kivy/tools/bitcoin_intent.xml
# (str) launchMode to set for the main activity
android.manifest.launch_mode = singleTask
# (list) Android additionnal libraries to copy into libs/armeabi
#android.add_libs_armeabi = lib/android/*.so

View File

@ -47,7 +47,6 @@ class ContextMenu(Bubble):
l = MenuItem()
l.text = _(k)
def func(f=v):
Clock.schedule_once(lambda dt: self.hide(), 0.1)
Clock.schedule_once(lambda dt: f(obj), 0.15)
l.on_release = func
self.ids.buttons.add_widget(l)

View File

@ -143,7 +143,7 @@ class InfoBubble(Factory.Bubble):
else:
Window.add_widget(self)
# wait for the bubble to adjust it's size according to text then animate
# wait for the bubble to adjust its size according to text then animate
Clock.schedule_once(lambda dt: self._show(pos, duration))
def _show(self, pos, duration):

View File

@ -0,0 +1,183 @@
from kivy.app import App
from kivy.factory import Factory
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from decimal import Decimal
Builder.load_string('''
<AddressLabel@Label>
text_size: self.width, None
halign: 'left'
valign: 'top'
<AddressItem@CardItem>
address: ''
memo: ''
amount: ''
status: ''
BoxLayout:
spacing: '8dp'
height: '32dp'
orientation: 'vertical'
Widget
AddressLabel:
text: root.address
shorten: True
Widget
AddressLabel:
text: (root.amount if root.status == 'Funded' else root.status) + ' ' + root.memo
color: .699, .699, .699, 1
font_size: '13sp'
shorten: True
Widget
<AddressesDialog@Popup>
id: popup
title: _('Addresses')
message: ''
pr_status: 'Pending'
show_change: 0
show_used: 0
on_message:
self.update()
BoxLayout:
id:box
padding: '12dp', '70dp', '12dp', '12dp'
spacing: '12dp'
orientation: 'vertical'
size_hint: 1, 1.1
BoxLayout:
spacing: '6dp'
size_hint: 1, None
orientation: 'horizontal'
AddressFilter:
opacity: 1
size_hint: 1, None
height: self.minimum_height
spacing: '5dp'
AddressButton:
id: search
text: {0:_('Receiving'), 1:_('Change'), 2:_('All')}[root.show_change]
on_release:
root.show_change = (root.show_change + 1) % 3
Clock.schedule_once(lambda dt: root.update())
AddressFilter:
opacity: 1
size_hint: 1, None
height: self.minimum_height
spacing: '5dp'
AddressButton:
id: search
text: {0:_('All'), 1:_('Unused'), 2:_('Funded'), 3:_('Used')}[root.show_used]
on_release:
root.show_used = (root.show_used + 1) % 4
Clock.schedule_once(lambda dt: root.update())
AddressFilter:
opacity: 1
size_hint: 1, None
height: self.minimum_height
spacing: '5dp'
canvas.before:
Color:
rgba: 0.9, 0.9, 0.9, 1
AddressButton:
id: change
text: root.message if root.message else _('Search')
on_release: Clock.schedule_once(lambda dt: app.description_dialog(popup))
ScrollView:
GridLayout:
cols: 1
id: search_container
size_hint_y: None
height: self.minimum_height
''')
from electrum_zcash_gui.kivy.i18n import _
from electrum_zcash_gui.kivy.uix.context_menu import ContextMenu
class EmptyLabel(Factory.Label):
pass
class AddressesDialog(Factory.Popup):
def __init__(self, app, screen, callback):
Factory.Popup.__init__(self)
self.app = app
self.screen = screen
self.callback = callback
self.cards = {}
self.context_menu = None
def get_card(self, addr, balance, is_used, label):
ci = self.cards.get(addr)
if ci is None:
ci = Factory.AddressItem()
ci.screen = self
ci.address = addr
self.cards[addr] = ci
ci.memo = label
ci.amount = self.app.format_amount_and_units(balance)
ci.status = _('Used') if is_used else _('Funded') if balance > 0 else _('Unused')
return ci
def update(self):
self.menu_actions = [(_('Use'), self.do_use), (_('Details'), self.do_view)]
wallet = self.app.wallet
if self.show_change == 0:
_list = wallet.get_receiving_addresses()
elif self.show_change == 1:
_list = wallet.get_change_addresses()
else:
_list = wallet.get_addresses()
search = self.message
container = self.ids.search_container
container.clear_widgets()
n = 0
for address in _list:
label = wallet.labels.get(address, '')
balance = sum(wallet.get_addr_balance(address))
is_used = wallet.is_used(address)
if self.show_used == 1 and (balance or is_used):
continue
if self.show_used == 2 and balance == 0:
continue
if self.show_used == 3 and not is_used:
continue
card = self.get_card(address, balance, is_used, label)
if search and not self.ext_search(card, search):
continue
container.add_widget(card)
n += 1
if not n:
msg = _('No address matching your search')
container.add_widget(EmptyLabel(text=msg))
def do_use(self, obj):
self.hide_menu()
self.dismiss()
self.app.show_request(obj.address)
def do_view(self, obj):
req = { 'address': obj.address, 'status' : obj.status }
status = obj.status
c, u, x = self.app.wallet.get_addr_balance(obj.address)
balance = c + u + x
if balance > 0:
req['fund'] = balance
self.app.show_addr_details(req, status)
def ext_search(self, card, search):
return card.memo.find(search) >= 0 or card.amount.find(search) >= 0
def show_menu(self, obj):
self.hide_menu()
self.context_menu = ContextMenu(obj, self.menu_actions)
self.ids.box.add_widget(self.context_menu)
def hide_menu(self):
if self.context_menu is not None:
self.ids.box.remove_widget(self.context_menu)
self.context_menu = None

View File

@ -13,21 +13,35 @@ Builder.load_string('''
anchor_x: 'center'
BoxLayout:
orientation: 'vertical'
size_hint: 0.8, 1
size_hint: 0.9, 1
Widget:
size_hint: 1, 0.2
BoxLayout:
size_hint: 1, None
height: '80dp'
Label:
id: a
btc_text: (kb.amount + ' ' + app.base_unit) if kb.amount else ''
fiat_text: (kb.fiat_amount + ' ' + app.fiat_unit) if kb.fiat_amount else ''
text1: ((self.fiat_text if kb.is_fiat else self.btc_text) if app.fiat_unit else self.btc_text) if self.btc_text else ''
text2: ((self.btc_text if kb.is_fiat else self.fiat_text) if app.fiat_unit else '') if self.btc_text else ''
text: self.text1 + "\\n" + "[color=#8888ff]" + self.text2 + "[/color]"
Button:
background_color: 0, 0, 0, 0
id: btc
text: kb.amount + ' ' + app.base_unit
color: (0.7, 0.7, 1, 1) if kb.is_fiat else (1, 1, 1, 1)
halign: 'right'
size_hint: 1, None
font_size: '22dp'
height: '80dp'
font_size: '20dp'
height: '48dp'
on_release:
kb.is_fiat = False
Button:
background_color: 0, 0, 0, 0
id: fiat
text: kb.fiat_amount + ' ' + app.fiat_unit
color: (1, 1, 1, 1) if kb.is_fiat else (0.7, 0.7, 1, 1)
halign: 'right'
size_hint: 1, None
font_size: '20dp'
height: '48dp'
disabled: not app.fx.is_enabled()
on_release:
kb.is_fiat = True
Widget:
size_hint: 1, 0.2
GridLayout:
@ -65,6 +79,9 @@ Builder.load_string('''
text: '0'
KButton:
text: '<'
Widget:
size_hint: 1, None
height: '48dp'
Button:
id: but_max
opacity: 1 if root.show_max else 0
@ -75,13 +92,6 @@ Builder.load_string('''
on_release:
kb.is_fiat = False
kb.amount = app.get_max_amount()
Button:
id: button_fiat
size_hint: 1, None
height: '48dp'
text: (app.base_unit if not kb.is_fiat else app.fiat_unit) if app.fiat_unit else ''
on_release:
if app.fiat_unit: popup.toggle_fiat(kb)
Button:
size_hint: 1, None
height: '48dp'
@ -102,7 +112,7 @@ Builder.load_string('''
height: '48dp'
text: _('OK')
on_release:
root.callback(a.btc_text)
root.callback(btc.text if kb.amount else '')
popup.dismiss()
''')
@ -117,9 +127,6 @@ class AmountDialog(Factory.Popup):
if amount:
self.ids.kb.amount = amount
def toggle_fiat(self, a):
a.is_fiat = not a.is_fiat
def update_amount(self, c):
kb = self.ids.kb
amount = kb.fiat_amount if kb.is_fiat else kb.amount

View File

@ -3,7 +3,6 @@ from kivy.factory import Factory
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from electrum_zcash.util import fee_levels
from electrum_zcash_gui.kivy.i18n import _
Builder.load_string('''
@ -12,29 +11,46 @@ Builder.load_string('''
title: _('Transaction Fees')
size_hint: 0.8, 0.8
pos_hint: {'top':0.9}
method: 0
BoxLayout:
orientation: 'vertical'
BoxLayout:
orientation: 'horizontal'
size_hint: 1, 0.5
Label:
id: fee_per_kb
text: _('Method') + ':'
Button:
text: _('Mempool') if root.method == 2 else _('ETA') if root.method == 1 else _('Static')
background_color: (0,0,0,0)
bold: True
on_release:
root.method = (root.method + 1) % 3
root.update_slider()
root.update_text()
BoxLayout:
orientation: 'horizontal'
size_hint: 1, 0.5
Label:
text: (_('Target') if root.method > 0 else _('Fee')) + ':'
Label:
id: fee_target
text: ''
Slider:
id: slider
range: 0, 4
step: 1
on_value: root.on_slider(self.value)
Widget:
size_hint: 1, 0.5
BoxLayout:
orientation: 'horizontal'
size_hint: 1, 0.5
Label:
text: _('Dynamic Fees')
CheckBox:
id: dynfees
on_active: root.on_checkbox(self.active)
TopLabel:
id: fee_estimate
text: ''
font_size: '14dp'
Widget:
size_hint: 1, 1
size_hint: 1, 0.5
BoxLayout:
orientation: 'horizontal'
size_hint: 1, 0.5
@ -58,55 +74,58 @@ class FeeDialog(Factory.Popup):
Factory.Popup.__init__(self)
self.app = app
self.config = config
self.fee_rate = self.config.fee_per_kb()
self.callback = callback
self.dynfees = self.config.get('dynamic_fees', True)
self.ids.dynfees.active = self.dynfees
mempool = self.config.use_mempool_fees()
dynfees = self.config.is_dynfee()
self.method = (2 if mempool else 1) if dynfees else 0
self.update_slider()
self.update_text()
def update_text(self):
value = int(self.ids.slider.value)
self.ids.fee_per_kb.text = self.get_fee_text(value)
pos = int(self.ids.slider.value)
dynfees, mempool = self.get_method()
if self.method == 2:
fee_rate = self.config.depth_to_fee(pos)
target, estimate = self.config.get_fee_text(pos, dynfees, mempool, fee_rate)
msg = 'In the current network conditions, in order to be positioned %s, a transaction will require a fee of %s.' % (target, estimate)
elif self.method == 1:
fee_rate = self.config.eta_to_fee(pos)
target, estimate = self.config.get_fee_text(pos, dynfees, mempool, fee_rate)
msg = 'In the last few days, transactions that confirmed %s usually paid a fee of at least %s.' % (target.lower(), estimate)
else:
fee_rate = self.config.static_fee(pos)
target, estimate = self.config.get_fee_text(pos, dynfees, True, fee_rate)
msg = 'In the current network conditions, a transaction paying %s would be positioned %s.' % (target, estimate)
self.ids.fee_target.text = target
self.ids.fee_estimate.text = msg
def get_method(self):
dynfees = self.method > 0
mempool = self.method == 2
return dynfees, mempool
def update_slider(self):
slider = self.ids.slider
if self.dynfees:
slider.range = (0, 4)
slider.step = 1
slider.value = self.config.get('fee_level', 2)
else:
slider.range = (0, 9)
slider.step = 1
slider.value = self.config.static_fee_index(self.fee_rate)
def get_fee_text(self, value):
if self.ids.dynfees.active:
tooltip = fee_levels[value]
if self.config.has_fee_estimates():
dynfee = self.config.dynfee(value)
tooltip += '\n' + (self.app.format_amount_and_units(dynfee)) + '/kB'
else:
fee_rate = self.config.static_fee(value)
tooltip = self.app.format_amount_and_units(fee_rate) + '/kB'
if self.config.has_fee_estimates():
i = self.config.reverse_dynfee(fee_rate)
tooltip += '\n' + (_('low fee') if i < 0 else 'Within %d blocks'%i)
return tooltip
dynfees, mempool = self.get_method()
maxp, pos, fee_rate = self.config.get_fee_slider(dynfees, mempool)
slider.range = (0, maxp)
slider.step = 1
slider.value = pos
def on_ok(self):
value = int(self.ids.slider.value)
self.config.set_key('dynamic_fees', self.dynfees, False)
if self.dynfees:
self.config.set_key('fee_level', value, True)
dynfees, mempool = self.get_method()
self.config.set_key('dynamic_fees', dynfees, False)
self.config.set_key('mempool_fees', mempool, False)
if dynfees:
if mempool:
self.config.set_key('depth_level', value, True)
else:
self.config.set_key('fee_level', value, True)
else:
self.config.set_key('fee_per_kb', self.config.static_fee(value), True)
self.callback()
def on_slider(self, value):
self.update_text()
def on_checkbox(self, b):
self.dynfees = b
self.update_slider()
self.update_text()

View File

@ -106,4 +106,6 @@ class FxDialog(Factory.Popup):
if ccy != self.fx.get_currency():
self.fx.set_currency(ccy)
self.app.fiat_unit = ccy
else:
self.app.is_fiat = False
Clock.schedule_once(lambda dt: self.add_exchanges())

View File

@ -135,7 +135,7 @@ Builder.load_string('''
height: self.minimum_height
Label:
color: root.text_color
text: _('From %d cosigners')%n.value
text: _('From {} cosigners').format(n.value)
Slider:
id: n
range: 2, 5
@ -143,7 +143,7 @@ Builder.load_string('''
value: 2
Label:
color: root.text_color
text: _('Require %d signatures')%m.value
text: _('Require {} signatures').format(m.value)
Slider:
id: m
range: 1, n.value
@ -613,7 +613,7 @@ class RestoreSeedDialog(WizardDialog):
for c in line.children:
if isinstance(c, Button):
if c.text in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
c.disabled = (c.text.lower() not in p) and last_word
c.disabled = (c.text.lower() not in p) and bool(last_word)
elif c.text == ' ':
c.disabled = not enable_space
@ -702,6 +702,7 @@ class AddXpubDialog(WizardDialog):
self.is_valid = kwargs['is_valid']
self.title = kwargs['title']
self.message = kwargs['message']
self.allow_multi = kwargs.get('allow_multi', False)
def check_text(self, dt):
self.ids.next.disabled = not bool(self.is_valid(self.get_text()))
@ -715,7 +716,10 @@ class AddXpubDialog(WizardDialog):
def scan_xpub(self):
def on_complete(text):
self.ids.text_input.text = text
if self.allow_multi:
self.ids.text_input.text += text + '\n'
else:
self.ids.text_input.text = text
self.app.scan_qr(on_complete)
def do_paste(self):
@ -798,28 +802,18 @@ class InstallWizard(BaseWizard, Widget):
app = App.get_running_app()
Clock.schedule_once(lambda dt: app.show_error(msg))
def password_dialog(self, message, callback):
def request_password(self, run_next, force_disable_encrypt_cb=False):
def on_success(old_pin, pin):
assert old_pin is None
run_next(pin, False)
def on_failure():
self.show_error(_('PIN mismatch'))
self.run('request_password', run_next)
popup = PasswordDialog()
popup.init(message, callback)
app = App.get_running_app()
popup.init(app, None, _('Choose PIN code'), on_success, on_failure, is_change=2)
popup.open()
def request_password(self, run_next):
def callback(pin):
if pin:
self.run('confirm_password', pin, run_next)
else:
run_next(None, None)
self.password_dialog('Choose a PIN code', callback)
def confirm_password(self, pin, run_next):
def callback(conf):
if conf == pin:
run_next(pin, False)
else:
self.show_error(_('PIN mismatch'))
self.run('request_password', run_next)
self.password_dialog('Confirm your PIN code', callback)
def action_dialog(self, action, run_next):
f = getattr(self, action)
f()

View File

@ -0,0 +1,169 @@
from kivy.app import App
from kivy.factory import Factory
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from decimal import Decimal
Builder.load_string('''
<InvoicesLabel@Label>
#color: .305, .309, .309, 1
text_size: self.width, None
halign: 'left'
valign: 'top'
<InvoiceItem@CardItem>
requestor: ''
memo: ''
amount: ''
status: ''
date: ''
icon: 'atlas://gui/kivy/theming/light/important'
Image:
id: icon
source: root.icon
size_hint: None, 1
width: self.height *.54
mipmap: True
BoxLayout:
spacing: '8dp'
height: '32dp'
orientation: 'vertical'
Widget
InvoicesLabel:
text: root.requestor
shorten: True
Widget
InvoicesLabel:
text: root.memo
color: .699, .699, .699, 1
font_size: '13sp'
shorten: True
Widget
BoxLayout:
spacing: '8dp'
height: '32dp'
orientation: 'vertical'
Widget
InvoicesLabel:
text: root.amount
font_size: '15sp'
halign: 'right'
width: '110sp'
Widget
InvoicesLabel:
text: root.status
font_size: '13sp'
halign: 'right'
color: .699, .699, .699, 1
Widget
<InvoicesDialog@Popup>
id: popup
title: _('Invoices')
BoxLayout:
id: box
orientation: 'vertical'
spacing: '1dp'
ScrollView:
GridLayout:
cols: 1
id: invoices_container
size_hint: 1, None
height: self.minimum_height
spacing: '2dp'
padding: '12dp'
''')
from kivy.properties import BooleanProperty
from electrum_zcash_gui.kivy.i18n import _
from electrum_zcash.util import format_time
from electrum_zcash.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
from electrum_zcash_gui.kivy.uix.context_menu import ContextMenu
invoice_text = {
PR_UNPAID:_('Pending'),
PR_UNKNOWN:_('Unknown'),
PR_PAID:_('Paid'),
PR_EXPIRED:_('Expired')
}
pr_icon = {
PR_UNPAID: 'atlas://gui/kivy/theming/light/important',
PR_UNKNOWN: 'atlas://gui/kivy/theming/light/important',
PR_PAID: 'atlas://gui/kivy/theming/light/confirmed',
PR_EXPIRED: 'atlas://gui/kivy/theming/light/close'
}
class InvoicesDialog(Factory.Popup):
def __init__(self, app, screen, callback):
Factory.Popup.__init__(self)
self.app = app
self.screen = screen
self.callback = callback
self.cards = {}
self.context_menu = None
def get_card(self, pr):
key = pr.get_id()
ci = self.cards.get(key)
if ci is None:
ci = Factory.InvoiceItem()
ci.key = key
ci.screen = self
self.cards[key] = ci
ci.requestor = pr.get_requestor()
ci.memo = pr.get_memo()
amount = pr.get_amount()
if amount:
ci.amount = self.app.format_amount_and_units(amount)
status = self.app.wallet.invoices.get_status(ci.key)
ci.status = invoice_text[status]
ci.icon = pr_icon[status]
else:
ci.amount = _('No Amount')
ci.status = ''
exp = pr.get_expiration_date()
ci.date = format_time(exp) if exp else _('Never')
return ci
def update(self):
self.menu_actions = [('Pay', self.do_pay), ('Details', self.do_view), ('Delete', self.do_delete)]
invoices_list = self.ids.invoices_container
invoices_list.clear_widgets()
_list = self.app.wallet.invoices.sorted_list()
for pr in _list:
ci = self.get_card(pr)
invoices_list.add_widget(ci)
def do_pay(self, obj):
self.hide_menu()
self.dismiss()
pr = self.app.wallet.invoices.get(obj.key)
self.app.on_pr(pr)
def do_view(self, obj):
pr = self.app.wallet.invoices.get(obj.key)
pr.verify(self.app.wallet.contacts)
self.app.show_pr_details(pr.get_dict(), obj.status, True)
def do_delete(self, obj):
from .question import Question
def cb(result):
if result:
self.app.wallet.invoices.remove(obj.key)
self.hide_menu()
self.update()
d = Question(_('Delete invoice?'), cb)
d.open()
def show_menu(self, obj):
self.hide_menu()
self.context_menu = ContextMenu(obj, self.menu_actions)
self.ids.box.add_widget(self.context_menu)
def hide_menu(self):
if self.context_menu is not None:
self.ids.box.remove_widget(self.context_menu)
self.context_menu = None

View File

@ -5,35 +5,42 @@ from kivy.lang import Builder
from decimal import Decimal
from kivy.clock import Clock
from electrum_zcash.util import InvalidPassword
from electrum_zcash_gui.kivy.i18n import _
Builder.load_string('''
<PasswordDialog@Popup>
id: popup
title: _('PIN Code')
title: 'Electrum-Zcash'
message: ''
size_hint: 0.9, 0.9
BoxLayout:
size_hint: 1, 1
orientation: 'vertical'
Widget:
size_hint: 1, 1
size_hint: 1, 0.05
Label:
font_size: '20dp'
text: root.message
text_size: self.width, None
size: self.texture_size
Widget:
size_hint: 1, 1
size_hint: 1, 0.05
Label:
id: a
text: ' * '*len(kb.password) + ' o '*(6-len(kb.password))
font_size: '50dp'
text: '*'*len(kb.password) + '-'*(6-len(kb.password))
size: self.texture_size
Widget:
size_hint: 1, 1
size_hint: 1, 0.05
GridLayout:
id: kb
size_hint: 1, None
height: self.minimum_height
update_amount: popup.update_password
password: ''
on_password: popup.on_password(self.password)
size_hint: 1, None
height: '200dp'
spacing: '2dp'
cols: 3
KButton:
text: '1'
@ -59,30 +66,44 @@ Builder.load_string('''
text: '0'
KButton:
text: '<'
BoxLayout:
size_hint: 1, None
height: '48dp'
Widget:
size_hint: 0.5, None
Button:
size_hint: 0.5, None
height: '48dp'
text: _('Cancel')
on_release:
popup.dismiss()
popup.callback(None)
''')
class PasswordDialog(Factory.Popup):
#def __init__(self, message, callback):
# Factory.Popup.__init__(self)
def init(self, message, callback):
def init(self, app, wallet, message, on_success, on_failure, is_change=0):
self.app = app
self.wallet = wallet
self.message = message
self.callback = callback
self.on_success = on_success
self.on_failure = on_failure
self.ids.kb.password = ''
self.success = False
self.is_change = is_change
self.pw = None
self.new_password = None
self.title = 'Electrum-Zcash' + (' - ' + self.wallet.basename() if self.wallet else '')
def check_password(self, password):
if self.is_change > 1:
return True
try:
self.wallet.check_password(password)
return True
except InvalidPassword as e:
return False
def on_dismiss(self):
if not self.success:
if self.on_failure:
self.on_failure()
else:
# keep dialog open
return True
else:
if self.on_success:
args = (self.pw, self.new_password) if self.is_change else (self.pw,)
Clock.schedule_once(lambda dt: self.on_success(*args), 0.1)
def update_password(self, c):
kb = self.ids.kb
@ -97,5 +118,25 @@ class PasswordDialog(Factory.Popup):
def on_password(self, pw):
if len(pw) == 6:
self.dismiss()
Clock.schedule_once(lambda dt: self.callback(pw), 0.1)
if self.check_password(pw):
if self.is_change == 0:
self.success = True
self.pw = pw
self.message = _('Please wait...')
self.dismiss()
elif self.is_change == 1:
self.pw = pw
self.message = _('Enter new PIN')
self.ids.kb.password = ''
self.is_change = 2
elif self.is_change == 2:
self.new_password = pw
self.message = _('Confirm new PIN')
self.ids.kb.password = ''
self.is_change = 3
elif self.is_change == 3:
self.success = pw == self.new_password
self.dismiss()
else:
self.app.show_error(_('Wrong PIN'))
self.ids.kb.password = ''

View File

@ -0,0 +1,157 @@
from kivy.app import App
from kivy.factory import Factory
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from decimal import Decimal
Builder.load_string('''
<RequestLabel@Label>
#color: .305, .309, .309, 1
text_size: self.width, None
halign: 'left'
valign: 'top'
<RequestItem@CardItem>
address: ''
memo: ''
amount: ''
status: ''
date: ''
icon: 'atlas://gui/kivy/theming/light/important'
Image:
id: icon
source: root.icon
size_hint: None, 1
width: self.height *.54
mipmap: True
BoxLayout:
spacing: '8dp'
height: '32dp'
orientation: 'vertical'
Widget
RequestLabel:
text: root.address
shorten: True
Widget
RequestLabel:
text: root.memo
color: .699, .699, .699, 1
font_size: '13sp'
shorten: True
Widget
BoxLayout:
spacing: '8dp'
height: '32dp'
orientation: 'vertical'
Widget
RequestLabel:
text: root.amount
halign: 'right'
font_size: '15sp'
Widget
RequestLabel:
text: root.status
halign: 'right'
font_size: '13sp'
color: .699, .699, .699, 1
Widget
<RequestsDialog@Popup>
id: popup
title: _('Requests')
BoxLayout:
id:box
orientation: 'vertical'
spacing: '1dp'
ScrollView:
GridLayout:
cols: 1
id: requests_container
size_hint: 1, None
height: self.minimum_height
spacing: '2dp'
padding: '12dp'
''')
from kivy.properties import BooleanProperty
from electrum_zcash_gui.kivy.i18n import _
from electrum_zcash.util import format_time
from electrum_zcash.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
from electrum_zcash_gui.kivy.uix.context_menu import ContextMenu
pr_icon = {
PR_UNPAID: 'atlas://gui/kivy/theming/light/important',
PR_UNKNOWN: 'atlas://gui/kivy/theming/light/important',
PR_PAID: 'atlas://gui/kivy/theming/light/confirmed',
PR_EXPIRED: 'atlas://gui/kivy/theming/light/close'
}
request_text = {
PR_UNPAID: _('Pending'),
PR_UNKNOWN: _('Unknown'),
PR_PAID: _('Received'),
PR_EXPIRED: _('Expired')
}
class RequestsDialog(Factory.Popup):
def __init__(self, app, screen, callback):
Factory.Popup.__init__(self)
self.app = app
self.screen = screen
self.callback = callback
self.cards = {}
self.context_menu = None
def get_card(self, req):
address = req['address']
ci = self.cards.get(address)
if ci is None:
ci = Factory.RequestItem()
ci.address = address
ci.screen = self
self.cards[address] = ci
amount = req.get('amount')
ci.amount = self.app.format_amount_and_units(amount) if amount else ''
ci.memo = req.get('memo', '')
status, conf = self.app.wallet.get_request_status(address)
ci.status = request_text[status]
ci.icon = pr_icon[status]
#exp = pr.get_expiration_date()
#ci.date = format_time(exp) if exp else _('Never')
return ci
def update(self):
self.menu_actions = [(_('Show'), self.do_show), (_('Delete'), self.do_delete)]
requests_list = self.ids.requests_container
requests_list.clear_widgets()
_list = self.app.wallet.get_sorted_requests(self.app.electrum_config)
for pr in _list:
ci = self.get_card(pr)
requests_list.add_widget(ci)
def do_show(self, obj):
self.hide_menu()
self.dismiss()
self.app.show_request(obj.address)
def do_delete(self, req):
from .question import Question
def cb(result):
if result:
self.app.wallet.remove_payment_request(req.address, self.app.electrum_config)
self.hide_menu()
self.update()
d = Question(_('Delete request'), cb)
d.open()
def show_menu(self, obj):
self.hide_menu()
self.context_menu = ContextMenu(obj, self.menu_actions)
self.ids.box.add_widget(self.context_menu)
def hide_menu(self):
if self.context_menu is not None:
self.ids.box.remove_widget(self.context_menu)
self.context_menu = None

View File

@ -8,7 +8,6 @@ from electrum_zcash.i18n import languages
from electrum_zcash_gui.kivy.i18n import _
from electrum_zcash.plugins import run_hook
from electrum_zcash import coinchooser
from electrum_zcash.util import fee_levels
from .choice_dialog import ChoiceDialog
@ -37,9 +36,8 @@ Builder.load_string('''
action: partial(root.language_dialog, self)
CardSeparator
SettingsItem:
status: '' if root.disable_pin else ('ON' if root.use_encryption else 'OFF')
disabled: root.disable_pin
title: _('PIN code') + ': ' + self.status
title: _('PIN code')
description: _("Change your PIN code.")
action: partial(root.change_password, self)
CardSeparator
@ -49,12 +47,6 @@ Builder.load_string('''
description: _("Base unit for Zcash amounts.")
action: partial(root.unit_dialog, self)
CardSeparator
SettingsItem:
status: root.fee_status()
title: _('Fees') + ': ' + self.status
description: _("Fees paid to the Zcash miners.")
action: partial(root.fee_dialog, self)
CardSeparator
SettingsItem:
status: root.fx_status()
title: _('Fiat Currency') + ': ' + self.status
@ -80,12 +72,14 @@ Builder.load_string('''
description: _("Send your change to separate addresses.")
message: _('Send excess coins to change addresses')
action: partial(root.boolean_dialog, 'use_change', _('Use change addresses'), self.message)
CardSeparator
SettingsItem:
status: root.coinselect_status()
title: _('Coin selection') + ': ' + self.status
description: "Coin selection method"
action: partial(root.coinselect_dialog, self)
# disabled: there is currently only one coin selection policy
#CardSeparator
#SettingsItem:
# status: root.coinselect_status()
# title: _('Coin selection') + ': ' + self.status
# description: "Coin selection method"
# action: partial(root.coinselect_dialog, self)
''')
@ -101,7 +95,6 @@ class SettingsDialog(Factory.Popup):
layout.bind(minimum_height=layout.setter('height'))
# cached dialogs
self._fx_dialog = None
self._fee_dialog = None
self._proxy_dialog = None
self._language_dialog = None
self._unit_dialog = None
@ -192,18 +185,7 @@ class SettingsDialog(Factory.Popup):
d.open()
def fee_status(self):
if self.config.get('dynamic_fees', True):
return fee_levels[self.config.get('fee_level', 2)]
else:
return self.app.format_amount_and_units(self.config.fee_per_kb()) + '/kB'
def fee_dialog(self, label, dt):
if self._fee_dialog is None:
from .fee_dialog import FeeDialog
def cb():
label.status = self.fee_status()
self._fee_dialog = FeeDialog(self.app, self.config, cb)
self._fee_dialog.open()
return self.config.get_fee_status()
def boolean_dialog(self, name, title, message, dt):
from .checkbox_dialog import CheckBoxDialog

View File

@ -19,6 +19,7 @@ Builder.load_string('''
can_broadcast: False
fee_str: ''
date_str: ''
date_label:''
amount_str: ''
tx_hash: ''
status_str: ''
@ -45,7 +46,7 @@ Builder.load_string('''
text: _('Description') if root.description else ''
value: root.description
BoxLabel:
text: _('Date') if root.date_str else ''
text: root.date_label
value: root.date_str
BoxLabel:
text: _('Amount sent') if root.is_mine else _('Amount received')
@ -108,10 +109,13 @@ class TxDialog(Factory.Popup):
tx_hash, self.status_str, self.description, self.can_broadcast, amount, fee, height, conf, timestamp, exp_n = self.wallet.get_tx_info(self.tx)
self.tx_hash = tx_hash or ''
if timestamp:
self.date_label = _('Date')
self.date_str = datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
elif exp_n:
self.date_str = _('Within %d blocks') % exp_n if exp_n > 0 else _('unknown (low fee)')
self.date_label = _('Mempool depth')
self.date_str = _('{} from tip').format('%.2f MB'%(exp_n/1000000))
else:
self.date_label = ''
self.date_str = ''
if amount is None:

View File

@ -7,7 +7,7 @@ from kivy.uix.bubble import Bubble, BubbleButton
from kivy.properties import ListProperty
from kivy.uix.widget import Widget
from electrum_zcash_gui.i18n import _
from electrum_zcash_gui.kivy.i18n import _
class ContextMenuItem(Widget):
'''abstract class

View File

@ -17,7 +17,7 @@ from kivy.lang import Builder
from kivy.factory import Factory
from kivy.utils import platform
from electrum_zcash.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds
from electrum_zcash.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds, Fiat
from electrum_zcash import bitcoin
from electrum_zcash.util import timestamp_to_datetime
from electrum_zcash.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
@ -27,8 +27,6 @@ from .context_menu import ContextMenu
from electrum_zcash_gui.kivy.i18n import _
class EmptyLabel(Factory.Label):
pass
class CScreen(Factory.Screen):
__events__ = ('on_activate', 'on_deactivate', 'on_enter', 'on_leave')
@ -87,9 +85,9 @@ class CScreen(Factory.Screen):
self.add_widget(self.context_menu)
# note: this list needs to be kept in sync with another in qt
TX_ICONS = [
"close",
"close",
"unconfirmed",
"close",
"unconfirmed",
"close",
@ -133,7 +131,6 @@ class HistoryScreen(CScreen):
status, status_str = self.app.wallet.get_tx_status(tx_hash, height, conf, timestamp)
icon = "atlas://gui/kivy/theming/light/" + TX_ICONS[status]
label = self.app.wallet.get_label(tx_hash) if tx_hash else _('Pruned transaction outputs')
date = timestamp_to_datetime(timestamp)
ri = self.cards.get(tx_hash)
if ri is None:
ri = Factory.HistoryItem()
@ -143,14 +140,16 @@ class HistoryScreen(CScreen):
ri.icon = icon
ri.date = status_str
ri.message = label
ri.value = value or 0
ri.amount = self.app.format_amount(value, True) if value is not None else '--'
ri.confirmations = conf
if self.app.fiat_unit and date:
rate = self.app.fx.history_rate(date)
if rate:
s = self.app.fx.value_str(value, rate)
ri.quote_text = '' if s is None else s + ' ' + self.app.fiat_unit
if value is not None:
ri.is_mine = value < 0
if value < 0: value = - value
ri.amount = self.app.format_amount_and_units(value)
if self.app.fiat_unit:
fx = self.app.fx
fiat_value = value / Decimal(bitcoin.COIN) * self.app.wallet.price_at_timestamp(tx_hash, fx.timestamp_rate)
fiat_value = Fiat(fiat_value, fx.ccy)
ri.quote_text = str(fiat_value)
return ri
def update(self, see_all=False):
@ -162,13 +161,8 @@ class HistoryScreen(CScreen):
count = 0
for item in history:
ri = self.get_card(*item)
count += 1
history_card.add_widget(ri)
if count == 0:
msg = _('This screen shows your list of transactions. It is currently empty.')
history_card.add_widget(EmptyLabel(text=msg))
class SendScreen(CScreen):
@ -215,7 +209,7 @@ class SendScreen(CScreen):
if not self.screen.address:
return
if self.screen.is_pr:
# it sould be already saved
# it should be already saved
return
# save address as invoice
from electrum_zcash.paymentrequest import make_unsigned_request, PaymentRequest
@ -225,7 +219,6 @@ class SendScreen(CScreen):
pr = make_unsigned_request(req).SerializeToString()
pr = PaymentRequest(pr)
self.app.wallet.invoices.add(pr)
self.app.update_tab('invoices')
self.app.show_info(_("Invoice saved"))
if pr.is_pr():
self.screen.is_pr = True
@ -294,7 +287,7 @@ class SendScreen(CScreen):
def on_success(tx):
if tx.is_complete():
self.app.broadcast(tx, self.payment_request)
self.app.wallet.set_label(tx.hash(), message)
self.app.wallet.set_label(tx.txid(), message)
else:
self.app.tx_dialog(tx)
def on_failure(error):
@ -372,215 +365,33 @@ class ReceiveScreen(CScreen):
def save_request(self):
addr = self.screen.address
if not addr:
return False
amount = self.screen.amount
message = self.screen.message
amount = self.app.get_amount(amount) if amount else 0
req = self.app.wallet.make_payment_request(addr, amount, message, None)
self.app.wallet.add_payment_request(req, self.app.electrum_config)
self.app.update_tab('requests')
try:
self.app.wallet.add_payment_request(req, self.app.electrum_config)
added_request = True
except Exception as e:
self.app.show_error(_('Error adding payment request') + ':\n' + str(e))
added_request = False
finally:
self.app.update_tab('requests')
return added_request
def on_amount_or_message(self):
self.save_request()
Clock.schedule_once(lambda dt: self.update_qr())
def do_new(self):
addr = self.get_new_address()
if not addr:
self.app.show_info(_('Please use the existing requests first.'))
else:
self.save_request()
self.app.show_info(_('New request added to your list.'))
invoice_text = {
PR_UNPAID:_('Pending'),
PR_UNKNOWN:_('Unknown'),
PR_PAID:_('Paid'),
PR_EXPIRED:_('Expired')
}
request_text = {
PR_UNPAID: _('Pending'),
PR_UNKNOWN: _('Unknown'),
PR_PAID: _('Received'),
PR_EXPIRED: _('Expired')
}
pr_icon = {
PR_UNPAID: 'atlas://gui/kivy/theming/light/important',
PR_UNKNOWN: 'atlas://gui/kivy/theming/light/important',
PR_PAID: 'atlas://gui/kivy/theming/light/confirmed',
PR_EXPIRED: 'atlas://gui/kivy/theming/light/close'
}
class InvoicesScreen(CScreen):
kvname = 'invoices'
cards = {}
def get_card(self, pr):
key = pr.get_id()
ci = self.cards.get(key)
if ci is None:
ci = Factory.InvoiceItem()
ci.key = key
ci.screen = self
self.cards[key] = ci
ci.requestor = pr.get_requestor()
ci.memo = pr.get_memo()
amount = pr.get_amount()
if amount:
ci.amount = self.app.format_amount_and_units(amount)
status = self.app.wallet.invoices.get_status(ci.key)
ci.status = invoice_text[status]
ci.icon = pr_icon[status]
else:
ci.amount = _('No Amount')
ci.status = ''
exp = pr.get_expiration_date()
ci.date = format_time(exp) if exp else _('Never')
return ci
def update(self):
self.menu_actions = [('Pay', self.do_pay), ('Details', self.do_view), ('Delete', self.do_delete)]
invoices_list = self.screen.ids.invoices_container
invoices_list.clear_widgets()
_list = self.app.wallet.invoices.sorted_list()
for pr in _list:
ci = self.get_card(pr)
invoices_list.add_widget(ci)
if not _list:
msg = _('This screen shows the list of payment requests that have been sent to you. You may also use it to store contact addresses.')
invoices_list.add_widget(EmptyLabel(text=msg))
def do_pay(self, obj):
pr = self.app.wallet.invoices.get(obj.key)
self.app.on_pr(pr)
def do_view(self, obj):
pr = self.app.wallet.invoices.get(obj.key)
pr.verify(self.app.wallet.contacts)
self.app.show_pr_details(pr.get_dict(), obj.status, True)
def do_delete(self, obj):
from .dialogs.question import Question
def cb(result):
if result:
self.app.wallet.invoices.remove(obj.key)
self.app.update_tab('invoices')
d = Question(_('Delete invoice?'), cb)
d.open()
address_icon = {
'Pending' : 'atlas://gui/kivy/theming/light/important',
'Paid' : 'atlas://gui/kivy/theming/light/confirmed'
}
class AddressScreen(CScreen):
kvname = 'address'
cards = {}
def get_card(self, addr, balance, is_used, label):
ci = self.cards.get(addr)
if ci is None:
ci = Factory.AddressItem()
ci.screen = self
ci.address = addr
self.cards[addr] = ci
ci.memo = label
ci.amount = self.app.format_amount_and_units(balance)
request = self.app.wallet.get_payment_request(addr, self.app.electrum_config)
if is_used:
ci.status = _('Used')
elif request:
status, conf = self.app.wallet.get_request_status(addr)
requested_amount = request.get('amount')
# make sure that requested amount is > 0
if status == PR_PAID:
s = _('Request paid')
elif status == PR_UNPAID:
s = _('Request pending')
elif status == PR_EXPIRED:
s = _('Request expired')
else:
s = ''
ci.status = s + ': ' + self.app.format_amount_and_units(requested_amount)
else:
ci.status = _('Funded') if balance>0 else _('Unused')
return ci
def update(self):
self.menu_actions = [('Receive', self.do_show), ('Details', self.do_view)]
wallet = self.app.wallet
_list = wallet.get_change_addresses() if self.screen.show_change else wallet.get_receiving_addresses()
search = self.screen.message
container = self.screen.ids.search_container
container.clear_widgets()
n = 0
for address in _list:
label = wallet.labels.get(address, '')
balance = sum(wallet.get_addr_balance(address))
is_used = wallet.is_used(address)
if self.screen.show_used == 1 and (balance or is_used):
continue
if self.screen.show_used == 2 and balance == 0:
continue
if self.screen.show_used == 3 and not is_used:
continue
card = self.get_card(address, balance, is_used, label)
if search and not self.ext_search(card, search):
continue
container.add_widget(card)
n += 1
if not n:
msg = _('No address matching your search')
container.add_widget(EmptyLabel(text=msg))
def do_show(self, obj):
self.app.show_request(obj.address)
def do_view(self, obj):
req = self.app.wallet.get_payment_request(obj.address, self.app.electrum_config)
if req:
c, u, x = self.app.wallet.get_addr_balance(obj.address)
balance = c + u + x
if balance > 0:
req['fund'] = balance
status = req.get('status')
amount = req.get('amount')
address = req['address']
if amount:
status = req.get('status')
status = request_text[status]
else:
received_amount = self.app.wallet.get_addr_received(address)
status = self.app.format_amount_and_units(received_amount)
self.app.show_pr_details(req, status, False)
else:
req = { 'address': obj.address, 'status' : obj.status }
status = obj.status
c, u, x = self.app.wallet.get_addr_balance(obj.address)
balance = c + u + x
if balance > 0:
req['fund'] = balance
self.app.show_addr_details(req, status)
def do_delete(self, obj):
from .dialogs.question import Question
def cb(result):
if result:
self.app.wallet.remove_payment_request(obj.address, self.app.electrum_config)
self.update()
d = Question(_('Delete request?'), cb)
d.open()
def ext_search(self, card, search):
return card.memo.find(search) >= 0 or card.amount.find(search) >= 0
def do_save(self):
if self.save_request():
self.app.show_info(_('Request was saved.'))
class TabbedCarousel(Factory.TabbedPanel):
@ -642,7 +453,7 @@ class TabbedCarousel(Factory.TabbedPanel):
self.current_tab.state = "normal"
header.state = 'down'
self._current_tab = header
# set the carousel to load the appropriate slide
# 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:

View File

@ -19,56 +19,57 @@
<HistoryItem@CardItem>
icon: 'atlas://gui/kivy/theming/light/important'
message: ''
value: 0
is_mine: True
amount: '--'
amount_color: '#FF6657' if self.value < 0 else '#2EA442'
action: _('Sent') if self.is_mine else _('Received')
amount_color: '#FF6657' if self.is_mine else '#2EA442'
confirmations: 0
date: ''
quote_text: ''
spacing: '9dp'
Image:
id: icon
source: root.icon
size_hint: None, 1
width: self.height *.54
allow_stretch: True
width: self.height*1.5
mipmap: True
BoxLayout:
orientation: 'vertical'
Widget
CardLabel:
text: root.date
font_size: '14sp'
text:
u'[color={color}]{s}[/color]'.format(s='<<' if root.is_mine else '>>', color=root.amount_color)\
+ ' ' + root.action + ' ' + (root.quote_text if app.is_fiat else root.amount)
font_size: '15sp'
CardLabel:
color: .699, .699, .699, 1
font_size: '13sp'
font_size: '14sp'
shorten: True
text: root.message
text: root.date + ' ' + root.message
Widget
CardLabel:
halign: 'right'
font_size: '15sp'
size_hint: None, 1
width: '110sp'
markup: True
font_name: font_light
text:
u'[color={amount_color}]{sign}{amount} {unit}[/color]\n'\
u'[color=#B2B3B3][size=13sp]{qt}[/size]'\
u'[/color]'.format(amount_color=root.amount_color,\
amount=root.amount[1:], qt=root.quote_text, sign=root.amount[0],\
unit=app.base_unit)
HistoryScreen:
name: 'history'
content: content
ScrollView:
id: content
do_scroll_x: False
GridLayout
id: history_container
cols: 1
size_hint: 1, None
height: self.minimum_height
padding: '12dp'
spacing: '2dp'
BoxLayout:
orientation: 'vertical'
Button:
background_color: 0, 0, 0, 0
text: app.fiat_balance if app.is_fiat else app.balance
markup: True
color: .9, .9, .9, 1
font_size: '30dp'
bold: True
size_hint: 1, 0.25
on_release: app.is_fiat = not app.is_fiat if app.fx.is_enabled() else False
ScrollView:
id: content
do_scroll_x: False
size_hint: 1, 0.75
GridLayout
id: history_container
cols: 1
size_hint: 1, None
height: self.minimum_height

View File

@ -1,66 +0,0 @@
<InvoicesLabel@Label>
#color: .305, .309, .309, 1
text_size: self.width, None
halign: 'left'
valign: 'top'
<InvoiceItem@CardItem>
requestor: ''
memo: ''
amount: ''
status: ''
date: ''
icon: 'atlas://gui/kivy/theming/light/important'
Image:
id: icon
source: root.icon
size_hint: None, 1
width: self.height *.54
mipmap: True
BoxLayout:
spacing: '8dp'
height: '32dp'
orientation: 'vertical'
Widget
InvoicesLabel:
text: root.requestor
shorten: True
Widget
InvoicesLabel:
text: root.memo
color: .699, .699, .699, 1
font_size: '13sp'
shorten: True
Widget
BoxLayout:
spacing: '8dp'
height: '32dp'
orientation: 'vertical'
Widget
InvoicesLabel:
text: root.amount
font_size: '15sp'
halign: 'right'
width: '110sp'
Widget
InvoicesLabel:
text: root.status
font_size: '13sp'
halign: 'right'
color: .699, .699, .699, 1
Widget
InvoicesScreen:
name: 'invoices'
BoxLayout:
orientation: 'vertical'
spacing: '1dp'
ScrollView:
GridLayout:
cols: 1
id: invoices_container
size_hint: 1, None
height: self.minimum_height
spacing: '2dp'
padding: '12dp'

View File

@ -11,7 +11,7 @@ Popup:
height: self.minimum_height
padding: '10dp'
SettingsItem:
value: _("%d connections.")% app.num_nodes if app.num_nodes else _("Not connected")
value: _("{} connections.").format(app.num_nodes) if app.num_nodes else _("Not connected")
title: _("Status") + ': ' + self.value
description: _("Connections with Electrum-Zcash servers")
action: lambda x: None
@ -46,7 +46,7 @@ Popup:
CardSeparator
SettingsItem:
title: _('Fork detected at block %d')%app.blockchain_checkpoint if app.num_chains>1 else _('No fork detected')
title: _('Fork detected at block {}').format(app.blockchain_checkpoint) if app.num_chains>1 else _('No fork detected')
fork_description: (_('You are following branch') if app.auto_connect else _("Your server is on branch")) + ' ' + app.blockchain_name
description: self.fork_description if app.num_chains>1 else _('Connected nodes are on the same chain')
action: app.choose_blockchain_dialog

View File

@ -70,7 +70,7 @@ ReceiveScreen:
id: address_label
text: s.address if s.address else _('Zcash Address')
shorten: True
disabled: True
on_release: Clock.schedule_once(lambda dt: app.addresses_dialog(s))
CardSeparator:
opacity: message_selection.opacity
color: blue_bottom.foreground_color
@ -110,16 +110,31 @@ ReceiveScreen:
BoxLayout:
size_hint: 1, None
height: '48dp'
IconButton:
icon: 'atlas://gui/kivy/theming/light/save'
size_hint: 0.6, None
height: '48dp'
on_release: s.parent.do_save()
Button:
text: _('Requests')
size_hint: 1, None
height: '48dp'
on_release: Clock.schedule_once(lambda dt: app.requests_dialog(s))
Button:
text: _('Copy')
size_hint: 1, None
height: '48dp'
on_release: s.parent.do_copy()
Button:
text: _('Share')
size_hint: 1, None
IconButton:
icon: 'atlas://gui/kivy/theming/light/share'
size_hint: 0.6, None
height: '48dp'
on_release: s.parent.do_share()
BoxLayout:
size_hint: 1, None
height: '48dp'
Widget
size_hint: 2, 1
Button:
text: _('New')
size_hint: 1, None

View File

@ -1,66 +0,0 @@
<RequestLabel@Label>
#color: .305, .309, .309, 1
text_size: self.width, None
halign: 'left'
valign: 'top'
<RequestItem@CardItem>
address: ''
memo: ''
amount: ''
status: ''
date: ''
icon: 'atlas://gui/kivy/theming/light/important'
Image:
id: icon
source: root.icon
size_hint: None, 1
width: self.height *.54
mipmap: True
BoxLayout:
spacing: '8dp'
height: '32dp'
orientation: 'vertical'
Widget
RequestLabel:
text: root.address
shorten: True
Widget
RequestLabel:
text: root.memo
color: .699, .699, .699, 1
font_size: '13sp'
shorten: True
Widget
BoxLayout:
spacing: '8dp'
height: '32dp'
orientation: 'vertical'
Widget
RequestLabel:
text: root.amount
halign: 'right'
font_size: '15sp'
Widget
RequestLabel:
text: root.status
halign: 'right'
font_size: '13sp'
color: .699, .699, .699, 1
Widget
RequestsScreen:
name: 'requests'
BoxLayout:
orientation: 'vertical'
spacing: '1dp'
ScrollView:
GridLayout:
cols: 1
id: requests_container
size_hint_y: None
height: self.minimum_height
spacing: '2dp'
padding: '12dp'

View File

@ -34,6 +34,7 @@ SendScreen:
text: s.address if s.address else _('Recipient')
shorten: True
on_release: Clock.schedule_once(lambda dt: app.show_info(_('Copy and paste the recipient address using the Paste button, or use the camera to scan a QR code.')))
#on_release: Clock.schedule_once(lambda dt: app.popup_dialog('contacts'))
CardSeparator:
opacity: int(not root.is_pr)
color: blue_bottom.foreground_color
@ -71,29 +72,51 @@ SendScreen:
text: s.message if s.message else (_('No Description') if root.is_pr else _('Description'))
disabled: root.is_pr
on_release: Clock.schedule_once(lambda dt: app.description_dialog(s))
CardSeparator:
opacity: int(not root.is_pr)
color: blue_bottom.foreground_color
BoxLayout:
size_hint: 1, None
height: blue_bottom.item_height
spacing: '5dp'
Image:
source: 'atlas://gui/kivy/theming/light/star_big_inactive'
opacity: 0.7
size_hint: None, None
size: '22dp', '22dp'
pos_hint: {'center_y': .5}
BlueButton:
id: fee_e
default_text: _('Fee')
text: app.fee_status
on_release: Clock.schedule_once(lambda dt: app.fee_dialog(s, True))
BoxLayout:
size_hint: 1, None
height: '48dp'
IconButton:
size_hint: 0.6, 1
on_release: s.parent.do_save()
icon: 'atlas://gui/kivy/theming/light/save'
Button:
text: _('Invoices')
size_hint: 1, 1
on_release: Clock.schedule_once(lambda dt: app.invoices_dialog(s))
Button:
text: _('Paste')
on_release: s.parent.do_paste()
IconButton:
id: qr
size_hint: 0.6, 1
on_release: Clock.schedule_once(lambda dt: app.scan_qr(on_complete=app.on_qr))
icon: 'atlas://gui/kivy/theming/light/camera'
Button:
text: _('Paste')
on_release: s.parent.do_paste()
Button:
text: _('Clear')
on_release: s.parent.do_clear()
IconButton:
size_hint: 0.6, 1
on_release: s.parent.do_save()
icon: 'atlas://gui/kivy/theming/light/save'
BoxLayout:
size_hint: 1, None
height: '48dp'
Button:
text: _('Clear')
on_release: s.parent.do_clear()
Widget:
size_hint: 2, 1
size_hint: 1, 1
Button:
text: _('Pay')
size_hint: 1, 1