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:
parent
4df22e559c
commit
59e3058de2
|
@ -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}`
|
|
@ -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
|
|
@ -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` .")
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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 |
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
|
@ -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 = ''
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue