Browse Source

remove the kivy gui

We now use the qml gui on Android, and haven't been maintaining
the kivy GUI for a while.
master
SomberNight 2 years ago
parent
commit
b45c84f24f
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 28
      .cirrus.yml
  2. 3
      .gitmodules
  3. 6
      contrib/android/Makefile
  4. 6
      contrib/android/Readme.md
  5. 4
      contrib/android/build.sh
  6. 256
      contrib/android/buildozer_kivy.spec
  7. 1
      contrib/android/buildozer_qml.spec
  8. 18
      contrib/android/p4a_recipes/kivy/__init__.py
  9. 19
      contrib/android/p4a_recipes/sdl2/__init__.py
  10. 18
      contrib/android/p4a_recipes/sdl2_image/__init__.py
  11. 18
      contrib/android/p4a_recipes/sdl2_mixer/__init__.py
  12. 18
      contrib/android/p4a_recipes/sdl2_ttf/__init__.py
  13. 7
      electrum/base_wizard.py
  14. 2
      electrum/commands.py
  15. 1
      electrum/gui/__init__.py
  16. 6
      electrum/gui/default_lang.py
  17. 92
      electrum/gui/kivy/__init__.py
  18. BIN
      electrum/gui/kivy/data/background.png
  19. BIN
      electrum/gui/kivy/data/fonts/Roboto-Bold.ttf
  20. BIN
      electrum/gui/kivy/data/fonts/Roboto-Condensed.ttf
  21. BIN
      electrum/gui/kivy/data/fonts/Roboto-Medium.ttf
  22. BIN
      electrum/gui/kivy/data/fonts/Roboto.ttf
  23. 4
      electrum/gui/kivy/data/fonts/tron/License.txt
  24. 21
      electrum/gui/kivy/data/fonts/tron/Readme.txt
  25. BIN
      electrum/gui/kivy/data/fonts/tron/Tr2n.ttf
  26. 4
      electrum/gui/kivy/data/glsl/default.fs
  27. BIN
      electrum/gui/kivy/data/glsl/default.png
  28. 6
      electrum/gui/kivy/data/glsl/default.vs
  29. 10
      electrum/gui/kivy/data/glsl/header.fs
  30. 17
      electrum/gui/kivy/data/glsl/header.vs
  31. BIN
      electrum/gui/kivy/data/images/defaulttheme-0.png
  32. 1
      electrum/gui/kivy/data/images/defaulttheme.atlas
  33. 89
      electrum/gui/kivy/data/java-classes/org/electrum/qr/SimpleScannerActivity.java
  34. BIN
      electrum/gui/kivy/data/logo/kivy-icon-32.png
  35. 755
      electrum/gui/kivy/data/style.kv
  36. 56
      electrum/gui/kivy/i18n.py
  37. 551
      electrum/gui/kivy/main.kv
  38. 1523
      electrum/gui/kivy/main_window.py
  39. 48
      electrum/gui/kivy/nfc_scanner/__init__.py
  40. 242
      electrum/gui/kivy/nfc_scanner/scanner_android.py
  41. 53
      electrum/gui/kivy/nfc_scanner/scanner_dummy.py
  42. BIN
      electrum/gui/kivy/theming/light/action_bar.png
  43. BIN
      electrum/gui/kivy/theming/light/action_button_group.png
  44. BIN
      electrum/gui/kivy/theming/light/action_group_dark.png
  45. BIN
      electrum/gui/kivy/theming/light/action_group_light.png
  46. BIN
      electrum/gui/kivy/theming/light/add_contact.png
  47. BIN
      electrum/gui/kivy/theming/light/arrow_back.png
  48. BIN
      electrum/gui/kivy/theming/light/bit_logo.png
  49. BIN
      electrum/gui/kivy/theming/light/blue_bg_round_rb.png
  50. BIN
      electrum/gui/kivy/theming/light/btn_create_account.png
  51. BIN
      electrum/gui/kivy/theming/light/btn_create_act_disabled.png
  52. BIN
      electrum/gui/kivy/theming/light/btn_nfc.png
  53. BIN
      electrum/gui/kivy/theming/light/btn_send_address.png
  54. BIN
      electrum/gui/kivy/theming/light/btn_send_nfc.png
  55. BIN
      electrum/gui/kivy/theming/light/calculator.png
  56. BIN
      electrum/gui/kivy/theming/light/camera.png
  57. BIN
      electrum/gui/kivy/theming/light/card.png
  58. BIN
      electrum/gui/kivy/theming/light/card_bottom.png
  59. BIN
      electrum/gui/kivy/theming/light/card_btn.png
  60. BIN
      electrum/gui/kivy/theming/light/card_top.png
  61. BIN
      electrum/gui/kivy/theming/light/carousel_deselected.png
  62. BIN
      electrum/gui/kivy/theming/light/carousel_selected.png
  63. BIN
      electrum/gui/kivy/theming/light/clock1.png
  64. BIN
      electrum/gui/kivy/theming/light/clock2.png
  65. BIN
      electrum/gui/kivy/theming/light/clock3.png
  66. BIN
      electrum/gui/kivy/theming/light/clock4.png
  67. BIN
      electrum/gui/kivy/theming/light/clock5.png
  68. BIN
      electrum/gui/kivy/theming/light/close.png
  69. BIN
      electrum/gui/kivy/theming/light/closebutton.png
  70. BIN
      electrum/gui/kivy/theming/light/confirmed.png
  71. BIN
      electrum/gui/kivy/theming/light/contact.png
  72. BIN
      electrum/gui/kivy/theming/light/contact_overlay.png
  73. BIN
      electrum/gui/kivy/theming/light/copy.png
  74. BIN
      electrum/gui/kivy/theming/light/create_act_text.png
  75. BIN
      electrum/gui/kivy/theming/light/create_act_text_active.png
  76. BIN
      electrum/gui/kivy/theming/light/delete.png
  77. BIN
      electrum/gui/kivy/theming/light/dialog.png
  78. BIN
      electrum/gui/kivy/theming/light/dropdown_background.png
  79. BIN
      electrum/gui/kivy/theming/light/electrum_icon640.png
  80. BIN
      electrum/gui/kivy/theming/light/error.png
  81. BIN
      electrum/gui/kivy/theming/light/eye1.png
  82. BIN
      electrum/gui/kivy/theming/light/gear.png
  83. BIN
      electrum/gui/kivy/theming/light/globe.png
  84. BIN
      electrum/gui/kivy/theming/light/icon_border.png
  85. BIN
      electrum/gui/kivy/theming/light/important.png
  86. BIN
      electrum/gui/kivy/theming/light/info.png
  87. BIN
      electrum/gui/kivy/theming/light/lightblue_bg_round_lb.png
  88. BIN
      electrum/gui/kivy/theming/light/lightning.png
  89. 6
      electrum/gui/kivy/theming/light/lightning.svg
  90. BIN
      electrum/gui/kivy/theming/light/list.png
  91. BIN
      electrum/gui/kivy/theming/light/logo.png
  92. BIN
      electrum/gui/kivy/theming/light/logo_atom_dull.png
  93. BIN
      electrum/gui/kivy/theming/light/mail_icon.png
  94. BIN
      electrum/gui/kivy/theming/light/manualentry.png
  95. BIN
      electrum/gui/kivy/theming/light/network.png
  96. 6
      electrum/gui/kivy/theming/light/network.svg
  97. BIN
      electrum/gui/kivy/theming/light/nfc.png
  98. BIN
      electrum/gui/kivy/theming/light/nfc_clock.png
  99. BIN
      electrum/gui/kivy/theming/light/nfc_phone.png
  100. BIN
      electrum/gui/kivy/theming/light/nfc_stage_one.png
  101. Some files were not shown because too many files have changed in this diff Show More

28
.cirrus.yml

@ -180,34 +180,6 @@ task:
depends_on:
- Tox Python 3.8
task:
name: Android build (Kivy $APK_ARCH)
container:
dockerfile: contrib/android/Dockerfile
cpu: 2
memory: 2G
env:
APK_ARCH: arm64-v8a
packages_tld_folder_cache:
folder: packages
fingerprint_script:
- echo $CIRRUS_TASK_NAME && cat contrib/deterministic-build/requirements.txt && cat contrib/make_packages.sh
- git ls-files -s contrib/android/
p4a_cache:
folders:
- ".buildozer/android/platform/build-$APK_ARCH/packages"
- ".buildozer/android/platform/build-$APK_ARCH/build"
fingerprint_script:
# note: should *at least* depend on Dockerfile and p4a_recipes/, but contrib/android/ is simplest
- git ls-files -s contrib/android/
- echo "kivy $APK_ARCH"
build_script:
- ./contrib/android/make_apk.sh kivy "$APK_ARCH" debug
binaries_artifacts:
path: "dist/*"
depends_on:
- Tox Python 3.8
task:
name: Android build (QML $APK_ARCH)
container:

3
.gitmodules vendored

@ -4,6 +4,3 @@
[submodule "electrum/www"]
path = electrum/plugins/payserver/www
url = https://github.com/spesmilo/electrum-http.git
[submodule "electrum/gui/kivy/theming/atlas"]
path = electrum/gui/kivy/theming/atlas
url = https://github.com/spesmilo/electrum-kivy-atlas

6
contrib/android/Makefile

@ -19,13 +19,9 @@ export PYTHONHASHSEED := $(SOURCE_DATE_EPOCH)
export BUILD_DATE := $(shell LC_ALL=C TZ=UTC date +'%b %e %Y' -d @$(SOURCE_DATE_EPOCH))
export BUILD_TIME := $(shell LC_ALL=C TZ=UTC date +'%H:%M:%S' -d @$(SOURCE_DATE_EPOCH))
# needs kivy installed or in PYTHONPATH
.PHONY: theming apk clean
.PHONY: apk clean
theming:
#bash -c 'for i in network lightning; do convert -background none theming/light/$i.{svg,png}; done'
$(PYTHON) -m kivy.atlas ../../electrum/gui/kivy/theming/atlas/light 1024 ../../electrum/gui/kivy/theming/light/*.png
prepare:
# running pre build setup
# copy electrum to main.py

6
contrib/android/Readme.md

@ -107,12 +107,6 @@ sudo apt-get install qtvirtualkeyboard-plugin
Run electrum with the `-g` switch: `electrum -g qml`
### The Kivy GUI can be run directly on Linux Desktop. How?
Install Kivy.
Build atlas: `(cd contrib/android/; make theming)`
Run electrum with the `-g` switch: `electrum -g kivy`
### debug vs release build
If you just follow the instructions above, you will build the apk

4
contrib/android/build.sh

@ -17,12 +17,12 @@ BUILD_UID=$(/usr/bin/stat -c %u "$PROJECT_ROOT")
# check arguments
if [[ -n "$3" \
&& ( "$1" == "kivy" || "$1" == "qml" ) \
&& ( "$1" == "qml" ) \
&& ( "$2" == "all" || "$2" == "armeabi-v7a" || "$2" == "arm64-v8a" || "$2" == "x86" || "$2" == "x86_64" ) \
&& ( "$3" == "debug" || "$3" == "release" || "$3" == "release-unsigned" ) ]] ; then
info "arguments $*"
else
fail "usage: build.sh <kivy|qml> <arm64-v8a|armeabi-v7a|x86|x86_64|all> <debug|release|release-unsigned>"
fail "usage: build.sh <qml|...> <arm64-v8a|armeabi-v7a|x86|x86_64|all> <debug|release|release-unsigned>"
exit 1
fi

256
contrib/android/buildozer_kivy.spec

@ -1,256 +0,0 @@
[app]
# (str) Title of your application
title = Electrum
# (str) Package name
package.name = Electrum
# (str) Package domain (needed for android/ios packaging)
package.domain = org.electrum
# (str) Source code where the main.py live
source.dir = .
# (list) Source files to include (let empty to include all the files)
source.include_exts = py,png,jpg,kv,atlas,ttf,txt,gif,pem,mo,vs,fs,json,csv
# (list) Source files to exclude (let empty to not exclude anything)
source.exclude_exts = spec
# (list) List of directory to exclude (let empty to not exclude anything)
source.exclude_dirs = bin, build, dist, contrib,
electrum/tests,
electrum/gui/qt,
electrum/gui/kivy/theming/light,
packages/qdarkstyle,
packages/qtpy
# (list) List of exclusions using pattern matching
source.exclude_patterns = Makefile,setup*,
# not reproducible:
packages/aiohttp-*.dist-info/*,
packages/frozenlist-*.dist-info/*
# (str) Application versioning (method 1)
version.regex = APK_VERSION = '(.*)'
version.filename = %(source.dir)s/electrum/version.py
# (str) Application versioning (method 2)
#version = 1.9.8
# (list) Application requirements
# note: versions and hashes are pinned in ./p4a_recipes/*
requirements =
hostpython3,
python3,
android,
openssl,
plyer,
kivy,
libffi,
libsecp256k1,
cryptography
# (str) Presplash of the application
#presplash.filename = %(source.dir)s/gui/kivy/theming/splash.png
presplash.filename = %(source.dir)s/electrum/gui/icons/electrum_presplash.png
# (str) Icon of the application
icon.filename = %(source.dir)s/electrum/gui/icons/android_electrum_icon_legacy.png
icon.adaptive_foreground.filename = %(source.dir)s/electrum/gui/icons/android_electrum_icon_foreground.png
icon.adaptive_background.filename = %(source.dir)s/electrum/gui/icons/android_electrum_icon_background.png
# (str) Supported orientation (one of landscape, portrait or all)
orientation = portrait
# (bool) Indicate if the application should be fullscreen or not
fullscreen = False
#
# Android specific
#
# (list) Permissions
android.permissions = INTERNET, CAMERA, WRITE_EXTERNAL_STORAGE
# (int) Android API to use (compileSdkVersion)
# note: when changing, Dockerfile also needs to be changed to install corresponding build tools
android.api = 30
# (int) Android targetSdkVersion
android.target_sdk_version = 31
# (int) Minimum API required. You will need to set the android.ndk_api to be as low as this value.
android.minapi = 21
# (str) Android NDK version to use
android.ndk = 22b
# (int) Android NDK API to use (optional). This is the minimum API your app will support.
android.ndk_api = 21
# (bool) Use --private data storage (True) or --dir public storage (False)
android.private_storage = True
# (str) Android NDK directory (if empty, it will be automatically downloaded.)
android.ndk_path = /opt/android/android-ndk
# (str) Android SDK directory (if empty, it will be automatically downloaded.)
android.sdk_path = /opt/android/android-sdk
# (str) ANT directory (if empty, it will be automatically downloaded.)
android.ant_path = /opt/android/apache-ant
# (bool) If True, then skip trying to update the Android sdk
# This can be useful to avoid excess Internet downloads or save time
# when an update is due and you just want to test/build your package
# note(ghost43): probably needed for reproducibility. versions pinned in Dockerfile.
android.skip_update = True
# (bool) If True, then automatically accept SDK license
# agreements. This is intended for automation only. If set to False,
# the default, you will be shown the license when first running
# buildozer.
android.accept_sdk_license = True
# (str) Android entry point, default is ok for Kivy-based app
#android.entrypoint = org.renpy.android.PythonActivity
# (list) List of Java .jar files to add to the libs so that pyjnius can access
# their classes. Don't add jars that you do not need, since extra jars can slow
# down the build process. Allows wildcards matching, for example:
# OUYA-ODK/libs/*.jar
#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar
#android.add_jars = lib/android/zbar.jar
# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
android.add_src = electrum/gui/kivy/data/java-classes/
android.gradle_dependencies = me.dm7.barcodescanner:zxing:1.9.8
android.add_activities = org.electrum.qr.SimpleScannerActivity
# (str) python-for-android branch to use, if not master, useful to try
# not yet merged features.
#android.branch = master
# (str) OUYA Console category. Should be one of GAME or APP
# If you leave this blank, OUYA support will not be enabled
#android.ouya.category = GAME
# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png
# (str) XML file to include as an intent filters in <activity> tag
android.manifest.intent_filters = contrib/android/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
# (bool) Indicate whether the screen should stay on
# Don't forget to add the WAKE_LOCK permission if you set this to True
#android.wakelock = False
# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
# note: can be overwritten by APP_ANDROID_ARCH env var
#android.arch = armeabi-v7a
# (int) overrides automatic versionCode computation (used in build.gradle)
# this is not the same as app version and should only be edited if you know what you're doing
# android.numeric_version = 1
# (list) Android application meta-data to set (key=value format)
#android.meta_data =
# (list) Android library project to add (will be added in the
# project.properties automatically.)
#android.library_references =
android.whitelist = lib-dynload/_csv.so
# (bool) enables Android auto backup feature (Android API >=23)
android.allow_backup = False
#
# Python for android (p4a) specific
#
# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
p4a.source_dir = /opt/python-for-android
# (str) The directory in which python-for-android should look for your own build recipes (if any)
p4a.local_recipes = %(source.dir)s/contrib/android/p4a_recipes/
# (str) Filename to the hook for p4a
#p4a.hook =
# (str) Bootstrap to use for android builds
# p4a.bootstrap = sdl2
# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
#p4a.port =
#
# iOS specific
#
# (str) Name of the certificate to use for signing the debug version
# Get a list of available identities: buildozer ios list_identities
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"
# (str) Name of the certificate to use for signing the release version
#ios.codesign.release = %(ios.codesign.debug)s
[buildozer]
# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
log_level = 2
# (str) Path to build output (i.e. .apk, .ipa) storage
bin_dir = ./dist
# -----------------------------------------------------------------------------
# List as sections
#
# You can define all the "list" as [section:key].
# Each line will be considered as a option to the list.
# Let's take [app] / source.exclude_patterns.
# Instead of doing:
#
# [app]
# source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
#
# This can be translated into:
#
# [app:source.exclude_patterns]
# license
# data/audio/*.wav
# data/images/original/*
#
# -----------------------------------------------------------------------------
# Profiles
#
# You can extend section / key with a profile
# For example, you want to deploy a demo version of your application without
# HD content. You could first change the title to add "(demo)" in the name
# and extend the excluded directories to remove the HD content.
#
# [app@demo]
# title = My Application (demo)
#
# [app:source.exclude_patterns@demo]
# images/hd/*
#
# Then, invoke the command line with the "demo" profile:
#
# buildozer --profile demo android debug

1
contrib/android/buildozer_qml.spec

@ -61,7 +61,6 @@ requirements =
libzbar
# (str) Presplash of the application
#presplash.filename = %(source.dir)s/gui/kivy/theming/splash.png
presplash.filename = %(source.dir)s/electrum/gui/icons/electrum_presplash.png
# (str) Icon of the application

18
contrib/android/p4a_recipes/kivy/__init__.py

@ -1,18 +0,0 @@
from pythonforandroid.recipes.kivy import KivyRecipe
assert KivyRecipe.depends == ['sdl2', 'pyjnius', 'setuptools', 'python3']
assert KivyRecipe.python_depends == ['certifi']
class KivyRecipePinned(KivyRecipe):
# kivy master 2020-12-10 (2.0.0 plus a few bugfixes)
version = "2debbc3b1484b14824112986cb03b1072a60fbfc"
sha512sum = "6cabb77860e63059ab4b0663b87f6396fa9133839b42db754628fc9a55f10b8d759466110e0763fd8dac40a49a03af276cb93b05076471d12db796e679f33d1d"
# mv "python_depends" into "depends" to ensure we can control what versions get installed
depends = [*KivyRecipe.depends, *KivyRecipe.python_depends]
python_depends = []
recipe = KivyRecipePinned()

19
contrib/android/p4a_recipes/sdl2/__init__.py

@ -1,19 +0,0 @@
import os
from pythonforandroid.recipes.sdl2 import LibSDL2Recipe
from pythonforandroid.util import load_source
util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py'))
assert LibSDL2Recipe._version == "2.0.9"
assert LibSDL2Recipe.depends == ['sdl2_image', 'sdl2_mixer', 'sdl2_ttf']
assert LibSDL2Recipe.python_depends == []
class LibSDL2RecipePinned(util.InheritedRecipeMixin, LibSDL2Recipe):
md5sum = None
sha512sum = "a78a4708b2bb5b35a7c7b7501eb3bd60a9aa3bb95a3d84e57763df4a377185e7312a94b66321eef7ca0d17255e4b402fc950e83ef0dbbd08f14ff1194107dc10"
recipe = LibSDL2RecipePinned()

18
contrib/android/p4a_recipes/sdl2_image/__init__.py

@ -1,18 +0,0 @@
import os
from pythonforandroid.recipes.sdl2_image import LibSDL2Image
from pythonforandroid.util import load_source
util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py'))
assert LibSDL2Image._version == "2.0.4"
assert LibSDL2Image.depends == []
assert LibSDL2Image.python_depends == []
class LibSDL2ImageRecipePinned(util.InheritedRecipeMixin, LibSDL2Image):
sha512sum = "7320a5c9111908d402fbb0c12a49eb359a6db645c0c86839793ebb1a5b75eaca7c85eb96851f3a0b4a68a2f06363c8189555afd4f1048a4a41447370eddd7e6a"
recipe = LibSDL2ImageRecipePinned()

18
contrib/android/p4a_recipes/sdl2_mixer/__init__.py

@ -1,18 +0,0 @@
import os
from pythonforandroid.recipes.sdl2_mixer import LibSDL2Mixer
from pythonforandroid.util import load_source
util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py'))
assert LibSDL2Mixer._version == "2.0.4"
assert LibSDL2Mixer.depends == []
assert LibSDL2Mixer.python_depends == []
class LibSDL2MixerPinned(util.InheritedRecipeMixin, LibSDL2Mixer):
sha512sum = "98c56069640668aaececa63748de21fc8f243c7d06386c45c43d0ee472bbb2595ccda644d9886ce5b95c3a3dee3c0a96903cf9a89ddc18d38f041133470699a3"
recipe = LibSDL2MixerPinned()

18
contrib/android/p4a_recipes/sdl2_ttf/__init__.py

@ -1,18 +0,0 @@
import os
from pythonforandroid.recipes.sdl2_ttf import LibSDL2TTF
from pythonforandroid.util import load_source
util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py'))
assert LibSDL2TTF._version == "2.0.15"
assert LibSDL2TTF.depends == []
assert LibSDL2TTF.python_depends == []
class LibSDL2TTFPinned(util.InheritedRecipeMixin, LibSDL2TTF):
sha512sum = "30d685932c3dd6f2c94e2778357a5c502f0421374293d7102a64d92f9c7861229bf36bedf51c1a698b296a58c858ca442d97afb908b7df1592fc8d4f8ae8ddfd"
recipe = LibSDL2TTFPinned()

7
electrum/base_wizard.py

@ -92,7 +92,6 @@ class BaseWizard(Logger):
self._stack = [] # type: List[WizardStackItem]
self.plugin = None # type: Optional[BasePlugin]
self.keystores = [] # type: List[KeyStore]
self.is_kivy = config.GUI_NAME == 'kivy'
self.seed_type = None
def set_icon(self, icon):
@ -210,17 +209,15 @@ class BaseWizard(Logger):
('choose_seed_type', _('Create a new seed')),
('restore_from_seed', _('I already have a seed')),
('restore_from_key', _('Use a master key')),
('choose_hw_device', _('Use a hardware device')),
]
if not self.is_kivy:
choices.append(('choose_hw_device', _('Use a hardware device')))
else:
message = _('Add a cosigner to your multi-sig wallet')
choices = [
('restore_from_key', _('Enter cosigner key')),
('restore_from_seed', _('Enter cosigner seed')),
('choose_hw_device', _('Cosign with hardware device')),
]
if not self.is_kivy:
choices.append(('choose_hw_device', _('Cosign with hardware device')))
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)

2
electrum/commands.py

@ -1589,7 +1589,7 @@ def get_parser():
# gui
parser_gui = subparsers.add_parser('gui', description="Run Electrum's Graphical User Interface.", help="Run GUI (default)")
parser_gui.add_argument("url", nargs='?', default=None, help="bitcoin URI (or bip70 file)")
parser_gui.add_argument("-g", "--gui", dest=SimpleConfig.GUI_NAME.key(), help="select graphical user interface", choices=['qt', 'kivy', 'text', 'stdio', 'qml'])
parser_gui.add_argument("-g", "--gui", dest=SimpleConfig.GUI_NAME.key(), help="select graphical user interface", choices=['qt', 'text', 'stdio', 'qml'])
parser_gui.add_argument("-m", action="store_true", dest=SimpleConfig.GUI_QT_HIDE_ON_STARTUP.key(), default=False, help="hide GUI on startup")
parser_gui.add_argument("-L", "--lang", dest=SimpleConfig.LOCALIZATION_LANGUAGE.key(), default=None, help="default language used in GUI")
parser_gui.add_argument("--daemon", action="store_true", dest="daemon", default=False, help="keep daemon running after GUI is closed")

1
electrum/gui/__init__.py

@ -8,7 +8,6 @@ from typing import TYPE_CHECKING, Mapping, Optional
if TYPE_CHECKING:
from . import qt
from . import kivy
from electrum.simple_config import SimpleConfig
from electrum.daemon import Daemon
from electrum.plugin import Plugins

6
electrum/gui/default_lang.py

@ -31,10 +31,4 @@ def get_default_language(*, gui_name: Optional[str] = None) -> str:
except Exception:
name = QLocale.system().name()
return name if name in languages else "en_GB"
elif gui_name == "kivy":
if "ANDROID_DATA" not in os.environ:
return "en_UK"
# FIXME: CJK/Arabic/etc languages do not work at all with kivy due to font issues,
# so it is easiest to just default to English... (see #2032)
return "en_UK"
return ""

92
electrum/gui/kivy/__init__.py

@ -1,92 +0,0 @@
#!/usr/bin/env python
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2012 thomasv@gitorious
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Kivy GUI
import sys
import os
from typing import TYPE_CHECKING
from electrum import GuiImportError
KIVY_GUI_PATH = os.path.abspath(os.path.dirname(__file__))
os.environ['KIVY_DATA_DIR'] = os.path.join(KIVY_GUI_PATH, 'data')
try:
sys.argv = ['']
import kivy
except ImportError as e:
# This error ideally shouldn't be raised with pre-built packages
raise GuiImportError(
"Error: Could not import kivy. Please install it using the "
"instructions mentioned here `https://kivy.org/#download` .") from e
# minimum required version for kivy
kivy.require('1.8.0')
from electrum.logging import Logger
from electrum.gui import BaseElectrumGui
if TYPE_CHECKING:
from electrum.simple_config import SimpleConfig
from electrum.daemon import Daemon
from electrum.plugin import Plugins
class ElectrumGui(BaseElectrumGui, Logger):
def __init__(self, *, config: 'SimpleConfig', daemon: 'Daemon', plugins: 'Plugins'):
BaseElectrumGui.__init__(self, config=config, daemon=daemon, plugins=plugins)
Logger.__init__(self)
self.logger.debug('ElectrumGUI: initialising')
self.network = daemon.network
def main(self):
self.daemon.start_network()
from .main_window import ElectrumWindow
w = ElectrumWindow(
config=self.config,
network=self.network,
plugins=self.plugins,
gui_object=self,
)
w.run()
def stop(self) -> None:
from kivy.app import App
from kivy.clock import Clock
app = App.get_running_app()
if not app:
return
Clock.schedule_once(lambda dt: app.stop())
@classmethod
def version_info(cls):
ret = {
"kivy.version": kivy.__version__,
}
if hasattr(kivy, "__path__"):
ret["kivy.path"] = ", ".join(kivy.__path__ or [])
return ret

BIN
electrum/gui/kivy/data/background.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

BIN
electrum/gui/kivy/data/fonts/Roboto-Bold.ttf

Binary file not shown.

BIN
electrum/gui/kivy/data/fonts/Roboto-Condensed.ttf

Binary file not shown.

BIN
electrum/gui/kivy/data/fonts/Roboto-Medium.ttf

Binary file not shown.

BIN
electrum/gui/kivy/data/fonts/Roboto.ttf

Binary file not shown.

4
electrum/gui/kivy/data/fonts/tron/License.txt

@ -1,4 +0,0 @@
Copyright (c) 2010-2011, Jeff Bell [www.randombell.com] | [jeffbell@randombell.com].
This font may be distributed freely however must retain this document as well as the Readme.txt file.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is available with a FAQ at: http://scripts.sil.org/OFL

21
electrum/gui/kivy/data/fonts/tron/Readme.txt

@ -1,21 +0,0 @@
TR2N v1.3
ABOUT THE FONT:
A font based upon the poster text for TRON LEGACY.
The font is different from the pre-existing TRON font currently on the web. Similar in minor aspects but different in most. Style based upon text from different region posters.
UPDATE HISTORY:
3/7/11 - Adjusted the letter B (both lowercase and uppercase), capped off the ends of T, P and R, added a few more punctuation marks, as well as added the TR and TP ligature to allow for the solid bar connect as in the poster art.
1/22/11 - Made minor corrections to all previous letters and punctuation. Corrected issue with number 8's top filling in.
ABOUT THE AUTHOR:
Jeff Bell has produced fonts before, but this is the first one in over 10 years. His original 3 fonts were under the name DJ-JOHNNYRKA and include "CASPER", "BEVERLY HILLS COP", "THE GODFATHER" and "FIDDUMS FAMILY".
For more information on Jeff Bell and his work can be found online:
www.randombell.com
www.damovieman.deviantart.com
http://www.imdb.com/name/nm3983081/
http://www.vimeo.com/user4004969/videos

BIN
electrum/gui/kivy/data/fonts/tron/Tr2n.ttf

Binary file not shown.

4
electrum/gui/kivy/data/glsl/default.fs

@ -1,4 +0,0 @@
$HEADER$
void main (void){
gl_FragColor = frag_color * texture2D(texture0, tex_coord0);
}

BIN
electrum/gui/kivy/data/glsl/default.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 B

6
electrum/gui/kivy/data/glsl/default.vs

@ -1,6 +0,0 @@
$HEADER$
void main (void) {
frag_color = color * vec4(1.0, 1.0, 1.0, opacity);
tex_coord0 = vTexCoords0;
gl_Position = projection_mat * modelview_mat * vec4(vPosition.xy, 0.0, 1.0);
}

10
electrum/gui/kivy/data/glsl/header.fs

@ -1,10 +0,0 @@
#ifdef GL_ES
precision highp float;
#endif
/* Outputs from the vertex shader */
varying vec4 frag_color;
varying vec2 tex_coord0;
/* uniform texture samplers */
uniform sampler2D texture0;

17
electrum/gui/kivy/data/glsl/header.vs

@ -1,17 +0,0 @@
#ifdef GL_ES
precision highp float;
#endif
/* Outputs to the fragment shader */
varying vec4 frag_color;
varying vec2 tex_coord0;
/* vertex attributes */
attribute vec2 vPosition;
attribute vec2 vTexCoords0;
/* uniform variables */
uniform mat4 modelview_mat;
uniform mat4 projection_mat;
uniform vec4 color;
uniform float opacity;

BIN
electrum/gui/kivy/data/images/defaulttheme-0.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

1
electrum/gui/kivy/data/images/defaulttheme.atlas

@ -1 +0,0 @@
{"defaulttheme-0.png": {"progressbar_background": [391, 227, 24, 24], "tab_btn_disabled": [264, 137, 32, 32], "tab_btn_pressed": [366, 137, 32, 32], "image-missing": [152, 171, 48, 48], "splitter_h": [174, 123, 32, 7], "splitter_down": [501, 253, 7, 32], "splitter_disabled_down": [503, 291, 7, 32], "vkeyboard_key_down": [468, 137, 32, 32], "vkeyboard_disabled_key_down": [400, 137, 32, 32], "selector_right": [248, 223, 55, 62], "player-background": [2, 287, 103, 103], "selector_middle": [191, 223, 55, 62], "spinner": [235, 82, 29, 37], "tab_btn_disabled_pressed": [298, 137, 32, 32], "switch-button_disabled": [277, 291, 43, 32], "textinput_disabled_active": [372, 326, 64, 64], "splitter_grip": [36, 50, 12, 26], "vkeyboard_key_normal": [2, 44, 32, 32], "button_disabled": [80, 82, 29, 37], "media-playback-stop": [302, 171, 48, 48], "splitter": [501, 87, 7, 32], "splitter_down_h": [140, 123, 32, 7], "sliderh_background_disabled": [72, 132, 41, 37], "modalview-background": [464, 456, 45, 54], "button": [142, 82, 29, 37], "splitter_disabled": [502, 137, 7, 32], "checkbox_radio_disabled_on": [433, 87, 32, 32], "slider_cursor": [402, 171, 48, 48], "vkeyboard_disabled_background": [68, 221, 64, 64], "checkbox_disabled_on": [297, 87, 32, 32], "sliderv_background_disabled": [2, 78, 37, 41], "button_disabled_pressed": [111, 82, 29, 37], "audio-volume-muted": [102, 171, 48, 48], "close": [417, 231, 20, 20], "action_group_disabled": [452, 171, 33, 48], "vkeyboard_background": [2, 221, 64, 64], "checkbox_off": [331, 87, 32, 32], "tab_disabled": [305, 253, 96, 32], "sliderh_background": [115, 132, 41, 37], "switch-button": [322, 291, 43, 32], "tree_closed": [439, 231, 20, 20], "bubble_btn_pressed": [435, 291, 32, 32], "selector_left": [134, 223, 55, 62], "filechooser_file": [174, 326, 64, 64], "checkbox_radio_disabled_off": [399, 87, 32, 32], "checkbox_radio_on": [196, 137, 32, 32], "checkbox_on": [365, 87, 32, 32], "button_pressed": [173, 82, 29, 37], "audio-volume-high": [464, 406, 48, 48], "audio-volume-low": [2, 171, 48, 48], "progressbar": [305, 227, 32, 24], "previous_normal": [487, 187, 19, 32], "separator": [504, 342, 5, 48], "filechooser_folder": [240, 326, 64, 64], "checkbox_radio_off": [467, 87, 32, 32], "textinput_active": [306, 326, 64, 64], "textinput": [438, 326, 64, 64], "player-play-overlay": [122, 395, 117, 115], "media-playback-pause": [202, 171, 48, 48], "sliderv_background": [41, 78, 37, 41], "ring": [354, 402, 108, 108], "bubble_arrow": [487, 175, 16, 10], "slider_cursor_disabled": [352, 171, 48, 48], "checkbox_disabled_off": [469, 291, 32, 32], "action_group_down": [2, 121, 33, 48], "spinner_disabled": [204, 82, 29, 37], "splitter_disabled_h": [106, 123, 32, 7], "bubble": [107, 325, 65, 65], "media-playback-start": [252, 171, 48, 48], "vkeyboard_disabled_key_normal": [434, 137, 32, 32], "overflow": [230, 137, 32, 32], "tree_opened": [461, 231, 20, 20], "action_item": [339, 227, 24, 24], "bubble_btn": [401, 291, 32, 32], "audio-volume-medium": [52, 171, 48, 48], "action_group": [37, 121, 33, 48], "spinner_pressed": [266, 82, 29, 37], "filechooser_selected": [2, 392, 118, 118], "tab": [403, 253, 96, 32], "action_bar": [158, 133, 36, 36], "action_view": [365, 227, 24, 24], "tab_btn": [332, 137, 32, 32], "switch-background": [192, 291, 83, 32], "splitter_disabled_down_h": [72, 123, 32, 7], "action_item_down": [367, 291, 32, 32], "switch-background_disabled": [107, 291, 83, 32], "textinput_disabled": [241, 399, 111, 111], "splitter_grip_h": [483, 239, 26, 12]}}

89
electrum/gui/kivy/data/java-classes/org/electrum/qr/SimpleScannerActivity.java

@ -1,89 +0,0 @@
package org.electrum.qr;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.content.Intent;
import android.support.v4.app.ActivityCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import java.util.Arrays;
import me.dm7.barcodescanner.zxing.ZXingScannerView;
import com.google.zxing.Result;
import com.google.zxing.BarcodeFormat;
public class SimpleScannerActivity extends Activity implements ZXingScannerView.ResultHandler {
private static final int MY_PERMISSIONS_CAMERA = 1002;
private ZXingScannerView mScannerView = null;
final String TAG = "org.electrum.SimpleScannerActivity";
@Override
public void onResume() {
super.onResume();
if (this.hasPermission()) {
this.startCamera();
} else {
this.requestPermission();
}
}
@Override
public void onPause() {
super.onPause();
if (null != mScannerView) {
mScannerView.stopCamera(); // Stop camera on pause
}
}
private void startCamera() {
mScannerView = new ZXingScannerView(this); // Programmatically initialize the scanner view
mScannerView.setFormats(Arrays.asList(BarcodeFormat.QR_CODE));
setContentView(mScannerView); // Set the scanner view as the content view
mScannerView.setResultHandler(this); // Register ourselves as a handler for scan results.
mScannerView.startCamera(); // Start camera on resume
}
@Override
public void handleResult(Result rawResult) {
Intent resultIntent = new Intent();
resultIntent.putExtra("text", rawResult.getText());
resultIntent.putExtra("format", rawResult.getBarcodeFormat().toString());
setResult(Activity.RESULT_OK, resultIntent);
this.finish();
}
private boolean hasPermission() {
return (ActivityCompat.checkSelfPermission(this,
Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED);
}
private void requestPermission() {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
MY_PERMISSIONS_CAMERA);
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_CAMERA: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay!
this.startCamera();
} else {
// permission denied
this.finish();
}
return;
}
}
}
}

BIN
electrum/gui/kivy/data/logo/kivy-icon-32.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

755
electrum/gui/kivy/data/style.kv

@ -1,755 +0,0 @@
#:kivy 1.0
<Label>:
canvas:
Color:
rgba: self.disabled_color if self.disabled else (self.color if not self.markup else (1, 1, 1, 1))
Rectangle:
texture: self.texture
size: self.texture_size
pos: int(self.center_x - self.texture_size[0] / 2.), int(self.center_y - self.texture_size[1] / 2.)
<-Button,-ToggleButton>:
state_image: self.background_normal if self.state == 'normal' else self.background_down
disabled_image: self.background_disabled_normal if self.state == 'normal' else self.background_disabled_down
canvas:
Color:
rgba: self.background_color
BorderImage:
border: self.border
pos: self.pos
size: self.size
source: self.disabled_image if self.disabled else self.state_image
Color:
rgba: self.disabled_color if self.disabled else self.color
Rectangle:
texture: self.texture
size: self.texture_size
pos: int(self.center_x - self.texture_size[0] / 2.), int(self.center_y - self.texture_size[1] / 2.)
<BubbleContent>
opacity: .7 if self.disabled else 1
rows: 1
canvas:
Color:
rgba: self.parent.background_color if self.parent else (1, 1, 1, 1)
BorderImage:
border: self.parent.border if self.parent else (16, 16, 16, 16)
texture: root.parent._bk_img.texture if root.parent else None
size: self.size
pos: self.pos
<BubbleButton>:
background_normal: 'atlas://data/images/defaulttheme/bubble_btn'
background_down: 'atlas://data/images/defaulttheme/bubble_btn_pressed'
background_disabled_normal: 'atlas://data/images/defaulttheme/bubble_btn'
background_disabled_down: 'atlas://data/images/defaulttheme/bubble_btn_pressed'
border: (0, 0, 0, 0)
<Slider>:
canvas:
Color:
rgb: 1, 1, 1
BorderImage:
border: (0, 18, 0, 18) if self.orientation == 'horizontal' else (18, 0, 18, 0)
pos: (self.x + self.padding, self.center_y - sp(18)) if self.orientation == 'horizontal' else (self.center_x - 18, self.y + self.padding)
size: (self.width - self.padding * 2, sp(36)) if self.orientation == 'horizontal' else (sp(36), self.height - self.padding * 2)
source: 'atlas://data/images/defaulttheme/slider{}_background{}'.format(self.orientation[0], '_disabled' if self.disabled else '')
Rectangle:
pos: (self.value_pos[0] - sp(16), self.center_y - sp(17)) if self.orientation == 'horizontal' else (self.center_x - (16), self.value_pos[1] - sp(16))
size: (sp(32), sp(32))
source: 'atlas://data/images/defaulttheme/slider_cursor{}'.format('_disabled' if self.disabled else '')
<RelativeLayout>:
canvas.before:
PushMatrix
Translate:
xy: self.pos
canvas.after:
PopMatrix
<Image,AsyncImage>:
canvas:
Color:
rgba: self.color
Rectangle:
texture: self.texture
size: self.norm_image_size
pos: self.center_x - self.norm_image_size[0] / 2., self.center_y - self.norm_image_size[1] / 2.
<TabbedPanelContent>
rows: 1
padding: 3
canvas:
Color:
rgba: self.parent.background_color if self.parent else (1, 1, 1, 1)
BorderImage:
border: self.parent.border if self.parent else (16, 16, 16, 16)
source: (root.parent.background_disabled_image if self.disabled else root.parent.background_image) if root.parent else None
size: self.size
pos: self.pos
<TabbedPanelStrip>
rows: 1
<StripLayout>
padding: '2dp', '2dp', '2dp', '2dp'
canvas.before:
BorderImage:
pos: self.pos
size: self.size
border: root.border
source: root.background_image
<TabbedPanelHeader>:
halign: 'center'
valign: 'middle'
background_normal: 'atlas://data/images/defaulttheme/tab_btn'
background_disabled_normal: 'atlas://data/images/defaulttheme/tab_btn_disabled'
background_down: 'atlas://data/images/defaulttheme/tab_btn_pressed'
background_disabled_down: 'atlas://data/images/defaulttheme/tab_btn_pressed'
border: (8, 8, 8, 8)
font_size: '15sp'
<Selector>
allow_stretch: True
<TextInput>:
canvas.before:
Color:
rgba: self.background_color
BorderImage:
border: self.border
pos: self.pos
size: self.size
source: (self.background_disabled_active if self.disabled else self.background_active) if self.focus else (self.background_disabled_normal if self.disabled else self.background_normal)
Color:
rgba: (self.cursor_color if self.focus and not self.cursor_blink else (0, 0, 0, 0))
Rectangle:
pos: [int(x) for x in self.cursor_pos]
size: 1, -self.line_height
Color:
rgba: self.disabled_foreground_color if self.disabled else (self.hint_text_color if not self.text and not self.focus else self.foreground_color)
<TextInputCutCopyPaste>:
but_cut: cut.__self__
but_copy: copy.__self__
but_paste: paste.__self__
but_selectall: selectall.__self__
size_hint: None, None
size: '150sp', '50sp'
BubbleButton:
id: cut
text: 'Cut'
on_release: root.do('cut')
BubbleButton:
id: copy
text: 'Copy'
on_release: root.do('copy')
BubbleButton:
id: paste
text: 'Paste'
on_release: root.do('paste')
BubbleButton:
id: selectall
text: 'Select All'
on_release: root.do('selectall')
<CodeInput>:
font_name: 'data/fonts/RobotoMono-Regular.ttf'
<TreeViewNode>:
canvas.before:
Color:
rgba: self.color_selected if self.is_selected else self.odd_color if self.odd else self.even_color
Rectangle:
pos: [self.parent.x, self.y] if self.parent else [0, 0]
size: [self.parent.width, self.height] if self.parent else [1, 1]
Color:
rgba: 1, 1, 1, int(not self.is_leaf)
Rectangle:
source: 'atlas://data/images/defaulttheme/tree_%s' % ('opened' if self.is_open else 'closed')
size: 16, 16
pos: self.x - 20, self.center_y - 8
canvas.after:
Color:
rgba: .5, .5, .5, .2
Line:
points: [self.parent.x, self.y, self.parent.right, self.y] if self.parent else []
<TreeViewLabel>:
width: self.texture_size[0]
height: max(self.texture_size[1] + dp(10), dp(24))
text_size: self.width, None
<StencilView>:
canvas.before:
StencilPush
Rectangle:
pos: self.pos
size: self.size
StencilUse
canvas.after:
StencilUnUse
Rectangle:
pos: self.pos
size: self.size
StencilPop
<FileChooserListLayout>:
on_entry_added: treeview.add_node(args[1])
on_entries_cleared: treeview.root.nodes = []
on_subentry_to_entry: not args[2].locked and treeview.add_node(args[1], args[2])
on_remove_subentry: args[2].nodes = []
BoxLayout:
pos: root.pos
size: root.size
size_hint: None, None
orientation: 'vertical'
BoxLayout:
size_hint_y: None
height: 30
orientation: 'horizontal'
Widget:
# Just for spacing
width: 10
size_hint_x: None
Label:
text: 'Name'
text_size: self.size
halign: 'left'
bold: True
Label:
text: 'Size'
text_size: self.size
size_hint_x: None
halign: 'right'
bold: True
Widget:
# Just for spacing
width: 10
size_hint_x: None
ScrollView:
id: scrollview
do_scroll_x: False
Scatter:
do_rotation: False
do_scale: False
do_translation: False
size: treeview.size
size_hint_y: None
TreeView:
id: treeview
hide_root: True
size_hint_y: None
width: scrollview.width
height: self.minimum_height
on_node_expand: root.controller.entry_subselect(args[1])
on_node_collapse: root.controller.close_subselection(args[1])
<FileChooserListView>:
layout: layout
FileChooserListLayout:
id: layout
controller: root
[FileListEntry@FloatLayout+TreeViewNode]:
locked: False
entries: []
path: ctx.path
# FIXME: is_selected is actually a read_only treeview property. In this
# case, however, we're doing this because treeview only has single-selection
# hardcoded in it. The fix to this would be to update treeview to allow
# multiple selection.
is_selected: self.path in ctx.controller().selection
orientation: 'horizontal'
size_hint_y: None
height: '48dp' if dp(1) > 1 else '24dp'
# Don't allow expansion of the ../ node
is_leaf: not ctx.isdir or ctx.name.endswith('..' + ctx.sep) or self.locked
on_touch_down: self.collide_point(*args[1].pos) and ctx.controller().entry_touched(self, args[1])
on_touch_up: self.collide_point(*args[1].pos) and ctx.controller().entry_released(self, args[1])
BoxLayout:
pos: root.pos
Label:
id: filename
text_size: self.width, None
halign: 'left'
shorten: True
text: ctx.name
Label:
text_size: self.width, None
size_hint_x: None
halign: 'right'
text: '{}'.format(ctx.get_nice_size())
<FileChooserIconLayout>:
on_entry_added: stacklayout.add_widget(args[1])
on_entries_cleared: stacklayout.clear_widgets()
ScrollView:
id: scrollview
pos: root.pos
size: root.size
size_hint: None, None
do_scroll_x: False
Scatter:
do_rotation: False
do_scale: False
do_translation: False
size_hint_y: None
height: stacklayout.height
StackLayout:
id: stacklayout
width: scrollview.width
size_hint_y: None
height: self.minimum_height
spacing: '10dp'
padding: '10dp'
<FileChooserIconView>:
layout: layout
FileChooserIconLayout:
id: layout
controller: root
[FileIconEntry@Widget]:
locked: False
path: ctx.path
selected: self.path in ctx.controller().selection
size_hint: None, None
on_touch_down: self.collide_point(*args[1].pos) and ctx.controller().entry_touched(self, args[1])
on_touch_up: self.collide_point(*args[1].pos) and ctx.controller().entry_released(self, args[1])
size: '100dp', '100dp'
canvas:
Color:
rgba: 1, 1, 1, 1 if self.selected else 0
BorderImage:
border: 8, 8, 8, 8
pos: root.pos
size: root.size
source: 'atlas://data/images/defaulttheme/filechooser_selected'
Image:
size: '48dp', '48dp'
source: 'atlas://data/images/defaulttheme/filechooser_%s' % ('folder' if ctx.isdir else 'file')
pos: root.x + dp(24), root.y + dp(40)
Label:
text: ctx.name
text_size: (root.width, self.height)
halign: 'center'
shorten: True
size: '100dp', '16dp'
pos: root.x, root.y + dp(16)
Label:
text: '{}'.format(ctx.get_nice_size())
font_size: '11sp'
color: .8, .8, .8, 1
size: '100dp', '16sp'
pos: root.pos
halign: 'center'
<FileChooserProgress>:
pos_hint: {'x': 0, 'y': 0}
canvas:
Color:
rgba: 0, 0, 0, .8
Rectangle:
pos: self.pos
size: self.size
Label:
pos_hint: {'x': .2, 'y': .6}
size_hint: .6, .2
text: 'Opening %s' % root.path
FloatLayout:
pos_hint: {'x': .2, 'y': .4}
size_hint: .6, .2
ProgressBar:
id: pb
pos_hint: {'x': 0, 'center_y': .5}
max: root.total
value: root.index
Label:
pos_hint: {'x': 0}
text: '%d / %d' % (root.index, root.total)
size_hint_y: None
height: self.texture_size[1]
y: pb.center_y - self.height - 8
font_size: '13sp'
color: (.8, .8, .8, .8)
AnchorLayout:
pos_hint: {'x': .2, 'y': .2}
size_hint: .6, .2
Button:
text: 'Cancel'
size_hint: None, None
size: 150, 44
on_release: root.cancel()
# Switch widget
<Switch>:
active_norm_pos: max(0., min(1., (int(self.active) + self.touch_distance / sp(41))))
canvas:
Color:
rgb: 1, 1, 1
Rectangle:
source: 'atlas://data/images/defaulttheme/switch-background{}'.format('_disabled' if self.disabled else '')
size: sp(83), sp(32)
pos: int(self.center_x - sp(41)), int(self.center_y - sp(16))
Rectangle:
source: 'atlas://data/images/defaulttheme/switch-button{}'.format('_disabled' if self.disabled else '')
size: sp(43), sp(32)
pos: int(self.center_x - sp(41) + self.active_norm_pos * sp(41)), int(self.center_y - sp(16))
# ModalView widget
<ModalView>:
canvas:
Color:
rgba: root.background_color[:3] + [root.background_color[-1] * self._anim_alpha]
Rectangle:
size: self._window.size if self._window else (0, 0)
Color:
rgb: 1, 1, 1
BorderImage:
source: root.background
border: root.border
pos: self.pos
size: self.size
# Popup widget
<Popup>:
_container: container
background_color: (0, 0, 0, 0.7)
GridLayout:
padding: '12dp'
cols: 1
size_hint: None, None
pos: root.pos
size: root.size
Label:
text: root.title
color: root.title_color
size_hint_y: None
height: self.texture_size[1] + dp(16)
text_size: self.width - dp(16), None
font_size: root.title_size
font_name: root.title_font
halign: root.title_align
Widget:
size_hint_y: None
height: dp(4)
canvas:
Color:
rgba: root.separator_color
Rectangle:
pos: self.x, self.y + root.separator_height / 2.
size: self.width, root.separator_height
BoxLayout:
id: container
# =============================================================================
# Spinner widget
# =============================================================================
<SpinnerOption>:
size_hint_y: None
height: '48dp'
<Spinner>:
background_normal: 'atlas://data/images/defaulttheme/spinner'
background_disabled_normal: 'atlas://data/images/defaulttheme/spinner_disabled'
background_down: 'atlas://data/images/defaulttheme/spinner_pressed'
# =============================================================================
# ActionBar widget
# =============================================================================
<ActionBar>:
height: '48dp'
size_hint_y: None
spacing: '4dp'
canvas:
Color:
rgba: self.background_color
BorderImage:
border: root.border
pos: self.pos
size: self.size
source: self.background_image
<ActionView>:
orientation: 'horizontal'
canvas:
Color:
rgba: self.background_color
BorderImage:
pos: self.pos
size: self.size
source: self.background_image
<ActionSeparator>:
size_hint_x: None
minimum_width: '2sp'
width: self.minimum_width
canvas:
Rectangle:
pos: self.x, self.y + sp(4)
size: self.width, self.height - sp(8)
source: self.background_image
<ActionButton,ActionToggleButton>:
background_normal: 'atlas://data/images/defaulttheme/' + ('action_bar' if self.inside_group else 'action_item')
background_down: 'atlas://data/images/defaulttheme/action_item_down'
size_hint_x: None if not root.inside_group else 1
width: [dp(48) if (root.icon and not root.inside_group) else max(dp(48), (self.texture_size[0] + dp(32))), self.size_hint_x][0]
color: self.color[:3] + [0 if (root.icon and not root.inside_group) else 1]
Image:
allow_stretch: True
opacity: 1 if (root.icon and not root.inside_group) else 0
source: root.icon
mipmap: root.mipmap
pos: root.x + dp(4), root.y + dp(4)
size: root.width - dp(8), root.height - sp(8)
<ActionLabel>:
size_hint_x: None if not root.inside_group else 1
width: self.texture_size[0] + dp(32)
<ActionGroup>:
size_hint_x: None
width: self.texture_size[0] + dp(32)
<ActionCheck>:
background_normal: 'atlas://data/images/defaulttheme/action_bar' if self.inside_group else 'atlas://data/images/defaulttheme/action_item'
<ActionPreviousImage@Image>:
temp_width: 0
temp_height: 0
<ActionPreviousButton@Button>:
background_normal: 'atlas://data/images/defaulttheme/action_item'
background_down: 'atlas://data/images/defaulttheme/action_item_down'
<ActionPrevious>:
size_hint_x: 1
minimum_width: layout.minimum_width + min(sp(100), title.width)
important: True
GridLayout:
id: layout
rows: 1
pos: root.pos
size_hint_x: None
width: self.minimum_width
ActionPreviousButton:
on_press: root.dispatch('on_press')
on_release: root.dispatch('on_release')
size_hint_x: None
width: prevlayout.width
GridLayout:
id: prevlayout
rows: 1
width: self.minimum_width
height: self.parent.height
pos: self.parent.pos
ActionPreviousImage:
id: prev_icon_image
source: root.previous_image
opacity: 1 if root.with_previous else 0
allow_stretch: True
size_hint_x: None
temp_width: root.previous_image_width or dp(prev_icon_image.texture_size[0])
temp_height: root.previous_image_height or dp(prev_icon_image.texture_size[1])
width:
(self.temp_width if self.temp_height <= self.height else \
self.temp_width * (self.height / self.temp_height)) \
if self.texture else dp(8)
mipmap: root.mipmap
ActionPreviousImage:
id: app_icon_image
source: root.app_icon
allow_stretch: True
size_hint_x: None
temp_width: root.app_icon_width or dp(app_icon_image.texture_size[0])
temp_height: root.app_icon_height or dp(app_icon_image.texture_size[1])
width:
(self.temp_width if self.temp_height <= self.height else \
self.temp_width * (self.height / self.temp_height)) \
if self.texture else dp(8)
mipmap: root.mipmap
Widget:
size_hint_x: None
width: '5sp'
Label:
id: title
text: root.title
text_size: self.size
color: root.color
shorten: True
shorten_from: 'right'
halign: 'left'
valign: 'middle'
<ActionGroup>:
background_normal: 'atlas://data/images/defaulttheme/action_group'
background_down: 'atlas://data/images/defaulttheme/action_group_down'
background_disabled_normal: 'atlas://data/images/defaulttheme/action_group_disabled'
border: 30, 30, 3, 3
ActionSeparator:
pos: root.pos
size: root.separator_width, root.height
opacity: 1 if root.use_separator else 0
background_image: root.separator_image if root.use_separator else 'action_view'
<ActionOverflow>:
border: 3, 3, 3, 3
background_normal: 'atlas://data/images/defaulttheme/action_item'
background_down: 'atlas://data/images/defaulttheme/action_item_down'
background_disabled_normal: 'atlas://data/images/defaulttheme/button_disabled'
size_hint_x: None
minimum_width: '48sp'
width: self.texture_size[0] if self.texture else self.minimum_width
canvas.after:
Color:
rgb: 1, 1, 1
Rectangle:
pos: root.center_x - sp(16), root.center_y - sp(16)
size: sp(32), sp(32)
source: root.overflow_image
<ActionDropDown>:
auto_width: False
# =============================================================================
# Accordion widget
# =============================================================================
[AccordionItemTitle@Label]:
text: ctx.title
normal_background: ctx.item.background_normal if ctx.item.collapse else ctx.item.background_selected
disabled_background: ctx.item.background_disabled_normal if ctx.item.collapse else ctx.item.background_disabled_selected
canvas.before:
Color:
rgba: self.disabled_color if self.disabled else self.color
BorderImage:
source: self.disabled_background if self.disabled else self.normal_background
pos: self.pos
size: self.size
PushMatrix
Translate:
xy: self.center_x, self.center_y
Rotate:
angle: 90 if ctx.item.orientation == 'horizontal' else 0
axis: 0, 0, 1
Translate:
xy: -self.center_x, -self.center_y
canvas.after:
PopMatrix
<AccordionItem>:
container: container
container_title: container_title
BoxLayout:
orientation: root.orientation
pos: root.pos
BoxLayout:
size_hint_x: None if root.orientation == 'horizontal' else 1
size_hint_y: None if root.orientation == 'vertical' else 1
width: root.min_space if root.orientation == 'horizontal' else 100
height: root.min_space if root.orientation == 'vertical' else 100
id: container_title
StencilView:
id: sv
BoxLayout:
id: container
pos: sv.pos
size: root.content_size
<ScrollView>:
_handle_y_pos: (self.right - self.bar_width - self.bar_margin) if self.bar_pos_y == 'right' else (self.x + self.bar_margin), self.y + self.height * self.vbar[0]
_handle_y_size: min(self.bar_width, self.width), self.height * self.vbar[1]
_handle_x_pos: self.x + self.width * self.hbar[0], (self.y + self.bar_margin) if self.bar_pos_x == 'bottom' else (self.top - self.bar_margin - self.bar_width)
_handle_x_size: self.width * self.hbar[1], min(self.bar_width, self.height)
canvas.after:
Color:
rgba: self._bar_color if (self.do_scroll_y and self.viewport_size[1] > self.height) else [0, 0, 0, 0]
Rectangle:
pos: root._handle_y_pos or (0, 0)
size: root._handle_y_size or (0, 0)
Color:
rgba: self._bar_color if (self.do_scroll_x and self.viewport_size[0] > self.width) else [0, 0, 0, 0]
Rectangle:
pos: root._handle_x_pos or (0, 0)
size: root._handle_x_size or (0, 0)
<CheckBox>:
_checkbox_state_image:
self.background_checkbox_down \
if self.active else self.background_checkbox_normal
_checkbox_disabled_image:
self.background_checkbox_disabled_down \
if self.active else self.background_checkbox_disabled_normal
_radio_state_image:
self.background_radio_down \
if self.active else self.background_radio_normal
_radio_disabled_image:
self.background_radio_disabled_down \
if self.active else self.background_radio_disabled_normal
_checkbox_image:
self._checkbox_disabled_image \
if self.disabled else self._checkbox_state_image
_radio_image:
self._radio_disabled_image \
if self.disabled else self._radio_state_image
canvas:
Color:
rgb: 1, 1, 1
Rectangle:
source: self._radio_image if self.group else self._checkbox_image
size: sp(32), sp(32)
pos: int(self.center_x - sp(16)), int(self.center_y - sp(16))
# =============================================================================
# Screen Manager
# =============================================================================
<ScreenManager>:
canvas.before:
StencilPush
Rectangle:
pos: self.pos
size: self.size
StencilUse
canvas.after:
StencilUnUse
Rectangle:
pos: self.pos
size: self.size
StencilPop

56
electrum/gui/kivy/i18n.py

@ -1,56 +0,0 @@
import gettext
from electrum.logging import get_logger
_logger = get_logger(__name__)
class _(str):
observers = set()
lang = None
def __new__(cls, s):
if _.lang is None:
_.switch_lang('en')
t = _.translate(s)
o = super(_, cls).__new__(cls, t)
o.source_text = s
return o
@staticmethod
def translate(s, *args, **kwargs):
return _.lang(s)
@staticmethod
def bind(label):
try:
_.observers.add(label)
except Exception:
pass
# garbage collection
new = set()
for label in _.observers:
try:
new.add(label)
except Exception:
pass
_.observers = new
@staticmethod
def switch_lang(lang):
_logger.info(f"switch_lang() called with {lang=!r}")
# get the right locales directory, and instantiate a gettext
from electrum.i18n import LOCALE_DIR, set_language
locales = gettext.translation('electrum', LOCALE_DIR, languages=[lang], fallback=True)
_.lang = locales.gettext
for label in _.observers:
try:
label.text = _(label.text.source_text)
except Exception:
pass
# Note that all invocations of _() inside the core electrum library
# use electrum.i18n instead of electrum.gui.kivy.i18n, so we should update the
# language there as well:
set_language(lang)

551
electrum/gui/kivy/main.kv

@ -1,551 +0,0 @@
#:import Clock kivy.clock.Clock
#:import Window kivy.core.window.Window
#:import Factory kivy.factory.Factory
#:import _ electrum.gui.kivy.i18n._
#:import KIVY_GUI_PATH electrum.gui.kivy.KIVY_GUI_PATH
###########################
# Global Defaults
###########################
<Label>
markup: True
font_name: 'Roboto'
font_size: '16sp'
bound: False
on_text: if isinstance(self.text, _) and not self.bound: self.bound=True; _.bind(self)
<TextInput>
on_focus: app._focused_widget = root
font_size: '18sp'
<Button>
on_parent: self.MIN_STATE_TIME = 0.1
<ListItemButton>
font_size: '12sp'
<Carousel>:
canvas.before:
Color:
rgba: 0.1, 0.1, 0.1, 1
Rectangle:
size: self.size
pos: self.pos
<ActionView>:
canvas.before:
Color:
rgba: 0.1, 0.1, 0.1, 1
Rectangle:
size: self.size
pos: self.pos
# Custom Global Widgets
<TopLabel>
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
<VGridLayout@GridLayout>:
rows: 1
size_hint: 1, None
height: self.minimum_height
<IconButton@Button>:
icon: ''
icon_size: '30dp'
AnchorLayout:
pos: self.parent.pos
size: self.parent.size
orientation: 'lr-tb'
Image:
source: self.parent.parent.icon
size_hint_x: None
size: root.icon_size, root.icon_size
<BackgroundColor@Widget>
background_color: 0, 0, 0, 1
canvas.before:
Color:
rgba: root.background_color
Rectangle:
size: self.size
pos: self.pos
<BackgroundTopLabel@TopLabel+BackgroundColor>
background_color: 0, 0, 0, 1
#########################
# Dialogs
#########################
<BoxLabel@BoxLayout>
text: ''
value: ''
size_hint_y: None
height: max(lbl1.height, lbl2.height)
TopLabel
id: lbl1
text: root.text
pos_hint: {'top':1}
TopLabel
id: lbl2
text: root.value
<BoxButton@BoxLayout>
text: ''
value: ''
size_hint_y: None
height: max(lbl1.height, lbl2.height)
TopLabel
id: lbl1
text: root.text
pos_hint: {'top':1}
Button
id: lbl2
text: root.value
background_color: (0,0,0,0)
bold: True
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
on_release:
root.callback()
<OutputItem>
address: ''
value: ''
background_color: 0, 0, 0, 1
color: 1, 1, 1, 1
size_hint_y: None
height: max(lbl1.height, lbl2.height)
BackgroundTopLabel
id: lbl1
text: '[ref=%s]%s[/ref]'%(root.address, root.address)
color: root.color
background_color: root.background_color
font_size: '6pt'
shorten: True
size_hint_x: 0.65
on_ref_press:
app._clipboard.copy(root.address)
app.show_info(_('Address copied to clipboard') + ' ' + root.address)
TopLabel
id: lbl2
text: root.value
font_size: '6pt'
size_hint_x: 0.35
halign: 'right'
<OutputList>
viewclass: 'OutputItem'
size_hint: 1, None
height: min(output_list_layout.minimum_height, dp(144))
scroll_type: ['bars', 'content']
bar_width: dp(15)
RecycleBoxLayout:
orientation: 'vertical'
default_size: None, pt(6)
default_size_hint: 1, None
size_hint: 1, None
height: self.minimum_height
id: output_list_layout
spacing: '10dp'
padding: '10dp'
canvas.before:
Color:
rgb: .3, .3, .3
Rectangle:
size: self.size
pos: self.pos
<RefLabel>
font_size: '6pt'
name: ''
data: ''
visible: True
opacity: 1 if self.visible else 0
disabled: not self.visible
text: self.data if self.data else _('Tap to show')
touched: False
padding: '10dp', '10dp'
background_color: .3, .3, .3, 1
show_text_with_qr: True
touch_callback: lambda: app.on_ref_label(self, show_text_with_qr=self.show_text_with_qr)
on_touch_down:
touch = args[1]
touched = bool(self.collide_point(*touch.pos))
if touched: self.touch_callback()
if touched: self.touched = True
canvas.before:
Color:
rgba: root.background_color
Rectangle:
size: self.size
pos: self.pos
<TxHashLabel@RefLabel>
data: ''
text: ' '.join(map(''.join, zip(*[iter(self.data)]*4))) if self.data else ''
<InfoBubble>
size_hint: None, None
width: '270dp' if root.fs else min(self.width, dp(270))
height: self.width if self.fs else (lbl.texture_size[1] + dp(27))
BoxLayout:
padding: '5dp' if root.fs else 0
Widget:
size_hint: None, 1
width: '4dp' if root.fs else '2dp'
Image:
id: img
source: root.icon
mipmap: True
size_hint: None, 1
width: (root.width - dp(20)) if root.fs else (0 if not root.icon else '32dp')
Widget:
size_hint_x: None
width: '5dp'
Label:
id: lbl
markup: True
font_size: '12sp'
text: root.message
text_size: self.width, None
valign: 'middle'
size_hint: 1, 1
width: 0 if root.fs else (root.width - img.width)
<SendReceiveBlueBottom@GridLayout>
item_height: dp(42)
foreground_color: .843, .914, .972, 1
cols: 1
padding: '12dp', 0
canvas.before:
Color:
rgba: 0.192, .498, 0.745, 1
BorderImage:
source: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/card_bottom'
size: self.size
pos: self.pos
<AddressFilter@GridLayout>
item_height: dp(42)
item_width: dp(60)
foreground_color: .843, .914, .972, 1
cols: 1
canvas.before:
Color:
rgba: 0.192, .498, 0.745, 1
BorderImage:
source: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/card_bottom'
size: self.size
pos: self.pos
<SearchBox@GridLayout>
item_height: dp(42)
foreground_color: .843, .914, .972, 1
cols: 1
padding: '12dp', 0
canvas.before:
Color:
rgba: 0.192, .498, 0.745, 1
BorderImage:
source: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/card_bottom'
size: self.size
pos: self.pos
<CardSeparator@Widget>
size_hint: 1, None
height: dp(1)
color: .909, .909, .909, 1
canvas:
Color:
rgba: root.color if root.color else (0, 0, 0, 0)
Rectangle:
size: self.size
pos: self.pos
<CardItem@ButtonBehavior+BoxLayout>
size_hint: 1, None
height: '65dp'
group: 'requests'
padding: dp(12)
spacing: dp(5)
screen: None
on_release: self.screen.show_item(args[0])
canvas.before:
Color:
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
<BlueButton@Button>:
background_color: 1, .585, .878, 0
halign: 'left'
text_size: (self.width-10, None)
size_hint: 0.5, None
default_text: ''
text: self.default_text
padding: '5dp', '5dp'
height: '40dp'
text_color: self.foreground_color
disabled_color: 1, 1, 1, 1
foreground_color: 1, 1, 1, 1
canvas.before:
Color:
rgba: (0.9, .498, 0.745, 1) if self.state == 'down' else self.background_color
Rectangle:
size: self.size
pos: self.pos
<KButton@Button>:
size_hint: 1, None
height: '60dp'
font_size: '30dp'
on_release:
self.parent.update_amount(self.text)
<StripLayout>
padding: 0, 0, 0, 0
<TabbedPanelStrip>:
on_parent:
if self.parent: self.parent.bar_width = 0
if self.parent: self.parent.scroll_x = 0.5
<TabbedCarousel>
carousel: carousel
do_default_tab: False
Carousel:
anim_type: 'out_quart'
min_move: .05
anim_move_duration: .1
anim_cancel_duration: .54
on_index: root.on_index(*args)
id: carousel
<CleanHeader@TabbedPanelHeader>
border: 16, 0, 16, 0
markup: False
text_size: self.size
halign: 'center'
valign: 'middle'
bold: True
font_size: '12.5sp'
background_normal: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/tab_btn'
background_down: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/tab_btn_pressed'
<ColoredLabel@Label>:
font_size: '48sp'
color: (.6, .6, .6, 1)
canvas.before:
Color:
rgb: (.9, .9, .9)
Rectangle:
pos: self.x + sp(2), self.y + sp(2)
size: self.width - sp(4), self.height - sp(4)
<SettingsItem@ButtonBehavior+BoxLayout>
orientation: 'vertical'
title: ''
description: ''
size_hint: 1, None
height: '60dp'
action: lambda x: None
canvas.before:
Color:
rgba: (0.192, .498, 0.745, 1) if self.state == 'down' else (0.3, 0.3, 0.3, 0)
Rectangle:
size: self.size
pos: self.pos
on_release:
Clock.schedule_once(self.action)
Widget
TopLabel:
id: title
text: self.parent.title
bold: True
halign: 'left'
TopLabel:
text: self.parent.description
color: 0.8, 0.8, 0.8, 1
halign: 'left'
Widget
<ScreenTabs@Screen>
TabbedCarousel:
id: panel
tab_height: '48dp'
tab_width: panel.width/3
strip_border: 0, 0, 0, 0
SendScreen:
id: send_screen
tab: send_tab
HistoryScreen:
id: history_screen
tab: history_tab
ReceiveScreen:
id: receive_screen
tab: receive_tab
CleanHeader:
id: send_tab
text: _('Send')
slide: 0
CleanHeader:
id: history_tab
text: _('History')
slide: 1
CleanHeader:
id: receive_tab
text: _('Receive')
slide: 2
<ActionOvrButton@ActionButton>
#on_release:
# 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)
self.state = 'normal'
<NetworkDialog@BoxLayout>
orientation: 'vertical'
ScrollView:
GridLayout:
id: scrollviewlayout
cols:1
size_hint: 1, None
height: self.minimum_height
padding: '10dp'
SettingsItem:
value: _("{} connections.").format(app.num_nodes) if app.num_nodes else _("Not connected")
title: _("Status") + ': ' + self.value
description: _("Connections with Electrum servers")
CardSeparator
SettingsItem:
title: _("Server") + ': ' + app.server_host
description: _("Server used to query your history.")
action: lambda x: app.popup_dialog('server')
CardSeparator
SettingsItem:
title: _("Proxy") + ': ' + app.proxy_str
description: _('Proxy configuration')
action: lambda x: app.popup_dialog('proxy')
CardSeparator
SettingsItem:
title: _("Auto-connect") + ': ' + ('ON' if app.auto_connect else 'OFF')
description: _("Select your server automatically")
action: app.toggle_auto_connect
CardSeparator
SettingsItem:
title: _("One-server mode") + ': ' + ('ON' if app.oneserver else 'OFF')
description: _("Only connect to a single server")
action: app.toggle_oneserver
disabled: app.auto_connect and not app.oneserver
CardSeparator
SettingsItem:
value: "%d blocks" % app.num_blocks
title: _("Blockchain") + ': ' + self.value
description: _('Verified block headers')
CardSeparator
SettingsItem:
title: _('Fork detected at block {}').format(app.blockchain_forkpoint) 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
disabled: app.num_chains == 1
BoxLayout:
orientation: 'vertical'
canvas.before:
Color:
rgb: .6, .6, .6
Rectangle:
size: self.size
source: f'{KIVY_GUI_PATH}/data/background.png'
ActionBar:
ActionView:
id: av
ActionPrevious:
with_previous: False
size_hint: None, None
size: 0, 0
ActionButton:
size_hint_x: None
text: app.wallet_name
bold: True
color: 0.7, 0.7, 0.7, 1
font_size: '22dp'
on_release:
Clock.schedule_once(lambda dt: app.popup_dialog('status'), 0.05)
self.state = 'normal'
ActionButton:
size_hint_x: 0.8
text: ''
opacity:0
ActionOverflow:
id: ao
size_hint_x: 0.2
ActionOvrButton:
name: 'about'
text: _('About')
ActionOvrButton:
name: 'wallets'
text: _('Wallets')
ActionOvrButton:
name: 'network'
text: _('Network')
disabled: app.network is None
ActionOvrButton:
name: 'addresses_dialog'
text: _('Addresses')
ActionOvrButton:
name: 'lightning_channels_dialog'
text: _('Channels')
ActionOvrButton:
name: 'settings'
text: _('Settings')
on_parent:
# when widget overflow drop down is shown, adjust the width
parent = args[1]
if parent: ao._dropdown.width = sp(200)
ScreenManager:
id: manager
ScreenTabs:
id: tabs

1523
electrum/gui/kivy/main_window.py

File diff suppressed because it is too large Load Diff

48
electrum/gui/kivy/nfc_scanner/__init__.py

@ -1,48 +0,0 @@
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.core import core_select_lib
__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
accessible and implemented by every platform implementation then define that
method in this class.
'''
payload = ObjectProperty(None)
'''This is the data gotten from the tag.
'''
def nfc_init(self):
''' Initialize the adapter.
'''
pass
def nfc_disable(self):
''' Disable scanning
'''
pass
def nfc_enable(self):
''' Enable Scanning
'''
pass
def nfc_enable_exchange(self, data):
''' Enable P2P Ndef exchange
'''
pass
def nfc_disable_exchange(self):
''' Disable/Stop P2P Ndef exchange
'''
pass
# load NFCScanner implementation
NFCScanner = core_select_lib('nfc_manager', (
# 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.gui.kivy')

242
electrum/gui/kivy/nfc_scanner/scanner_android.py

@ -1,242 +0,0 @@
'''This is the Android implementation of NFC Scanning using the
built in NFC adapter of some android phones.
'''
from kivy.app import App
from kivy.clock import Clock
#Detect which platform we are on
from kivy.utils import platform
if platform != 'android':
raise ImportError
import threading
from . import NFCBase
from jnius import autoclass, cast
from android.runnable import run_on_ui_thread
from android import activity
BUILDVERSION = autoclass('android.os.Build$VERSION').SDK_INT
NfcAdapter = autoclass('android.nfc.NfcAdapter')
PythonActivity = autoclass('org.kivy.android.PythonActivity')
JString = autoclass('java.lang.String')
Charset = autoclass('java.nio.charset.Charset')
locale = autoclass('java.util.Locale')
Intent = autoclass('android.content.Intent')
IntentFilter = autoclass('android.content.IntentFilter')
PendingIntent = autoclass('android.app.PendingIntent')
Ndef = autoclass('android.nfc.tech.Ndef')
NdefRecord = autoclass('android.nfc.NdefRecord')
NdefMessage = autoclass('android.nfc.NdefMessage')
app = None
class ScannerAndroid(NFCBase):
''' This is the class responsible for handling the interface with the
Android NFC adapter. See Module Documentation for details.
'''
name = 'NFCAndroid'
def nfc_init(self):
''' This is where we initialize NFC adapter.
'''
# Initialize NFC
global app
app = App.get_running_app()
# Make sure we are listening to new intent
activity.bind(on_new_intent=self.on_new_intent)
# Configure nfc
self.j_context = context = PythonActivity.mActivity
self.nfc_adapter = NfcAdapter.getDefaultAdapter(context)
# Check if adapter exists
if not self.nfc_adapter:
return False
# 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(
Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
# Filter for different types of action, by default we enable all.
# These are only for handling different NFC technologies when app is in foreground
self.ndef_detected = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED)
#self.tech_detected = IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)
#self.tag_detected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
# setup tag discovery for ourt tag type
try:
self.ndef_detected.addCategory(Intent.CATEGORY_DEFAULT)
# setup the foreground dispatch to detect all mime types
self.ndef_detected.addDataType('*/*')
self.ndef_exchange_filters = [self.ndef_detected]
except Exception as err:
raise Exception(repr(err))
return True
def get_ndef_details(self, tag):
''' Get all the details from the tag.
'''
details = {}
try:
#print 'id'
details['uid'] = ':'.join(['{:02x}'.format(bt & 0xff) for bt in tag.getId()])
#print 'technologies'
details['Technologies'] = tech_list = [tech.split('.')[-1] for tech in tag.getTechList()]
#print 'get NDEF tag details'
ndefTag = cast('android.nfc.tech.Ndef', Ndef.get(tag))
#print 'tag size'
details['MaxSize'] = ndefTag.getMaxSize()
#details['usedSize'] = '0'
#print 'is tag writable?'
details['writable'] = ndefTag.isWritable()
#print 'Data format'
# Can be made readonly
# get NDEF message details
ndefMesg = ndefTag.getCachedNdefMessage()
# get size of current records
details['consumed'] = len(ndefMesg.toByteArray())
#print 'tag type'
details['Type'] = ndefTag.getType()
# check if tag is empty
if not ndefMesg:
details['Message'] = None
return details
ndefrecords = ndefMesg.getRecords()
length = len(ndefrecords)
#print 'length', length
# will contain the NDEF record types
recTypes = []
for record in ndefrecords:
recTypes.append({
'type': ''.join(map(chr, record.getType())),
'payload': ''.join(map(chr, record.getPayload()))
})
details['recTypes'] = recTypes
except Exception as err:
print(str(err))
return details
def on_new_intent(self, intent):
''' 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.
'''
action_list = (NfcAdapter.ACTION_NDEF_DISCOVERED,)
# get TAG
#tag = cast('android.nfc.Tag', intent.getParcelableExtra(NfcAdapter.EXTRA_TAG))
#details = self.get_ndef_details(tag)
if intent.getAction() not in action_list:
print('unknow action, avoid.')
return
rawmsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
if not rawmsgs:
return
for message in rawmsgs:
message = cast(NdefMessage, message)
payload = message.getRecords()[0].getPayload()
print('payload: {}'.format(''.join(map(chr, payload))))
def nfc_disable(self):
'''Disable app from handling tags.
'''
self.disable_foreground_dispatch()
def nfc_enable(self):
'''Enable app to handle tags when app in foreground.
'''
self.enable_foreground_dispatch()
def create_AAR(self):
'''Create the record responsible for linking our application to the tag.
'''
return NdefRecord.createApplicationRecord(JString("org.electrum.kivy"))
def create_TNF_EXTERNAL(self, data):
'''Create our actual payload record.
'''
if BUILDVERSION >= 14:
domain = "org.electrum"
stype = "externalType"
extRecord = NdefRecord.createExternal(domain, stype, data)
else:
# Creating the NdefRecord manually:
extRecord = NdefRecord(
NdefRecord.TNF_EXTERNAL_TYPE,
"org.electrum:externalType",
'',
data)
return extRecord
def create_ndef_message(self, *recs):
''' Create the Ndef message that will be written to tag
'''
records = []
for record in recs:
if record:
records.append(record)
return NdefMessage(records)
@run_on_ui_thread
def disable_foreground_dispatch(self):
'''Disable foreground dispatch when app is paused.
'''
self.nfc_adapter.disableForegroundDispatch(self.j_context)
@run_on_ui_thread
def enable_foreground_dispatch(self):
'''Start listening for new tags
'''
self.nfc_adapter.enableForegroundDispatch(self.j_context,
self.nfc_pending_intent, self.ndef_exchange_filters, self.ndef_tech_list)
@run_on_ui_thread
def _nfc_enable_ndef_exchange(self, data):
# Enable p2p exchange
# Create record
ndef_record = NdefRecord(
NdefRecord.TNF_MIME_MEDIA,
'org.electrum.kivy', '', data)
# Create message
ndef_message = NdefMessage([ndef_record])
# Enable ndef push
self.nfc_adapter.enableForegroundNdefPush(self.j_context, ndef_message)
# Enable dispatch
self.nfc_adapter.enableForegroundDispatch(self.j_context,
self.nfc_pending_intent, self.ndef_exchange_filters, [])
@run_on_ui_thread
def _nfc_disable_ndef_exchange(self):
# Disable p2p exchange
self.nfc_adapter.disableForegroundNdefPush(self.j_context)
self.nfc_adapter.disableForegroundDispatch(self.j_context)
def nfc_enable_exchange(self, data):
'''Enable Ndef exchange for p2p
'''
self._nfc_enable_ndef_exchange()
def nfc_disable_exchange(self):
''' Disable Ndef exchange for p2p
'''
self._nfc_disable_ndef_exchange()

53
electrum/gui/kivy/nfc_scanner/scanner_dummy.py

@ -1,53 +0,0 @@
''' Dummy NFC Provider to be used on desktops in case no other provider is found
'''
from . import NFCBase
from kivy.clock import Clock
from kivy.logger import Logger
from kivy.app import App
class ScannerDummy(NFCBase):
'''This is the dummy interface that gets selected in case any other
hardware interface to NFC is not available.
'''
_initialised = False
name = 'NFCDummy'
def nfc_init(self):
# print 'nfc_init()'
Logger.debug('NFC: configure nfc')
self._initialised = True
self.nfc_enable()
return True
def on_new_intent(self, dt):
tag_info = {'type': 'dymmy',
'message': 'dummy',
'extra details': None}
# let Main app know that a tag has been detected
app = App.get_running_app()
app.tag_discovered(tag_info)
app.show_info('New tag detected.', duration=2)
Logger.debug('NFC: got new dummy tag')
def nfc_enable(self):
Logger.debug('NFC: enable')
if self._initialised:
Clock.schedule_interval(self.on_new_intent, 22)
def nfc_disable(self):
# print 'nfc_enable()'
Clock.unschedule(self.on_new_intent)
def nfc_enable_exchange(self, data):
''' Start sending data
'''
Logger.debug('NFC: sending data {}'.format(data))
def nfc_disable_exchange(self):
''' Disable/Stop ndef exchange
'''
Logger.debug('NFC: disable nfc exchange')

BIN
electrum/gui/kivy/theming/light/action_bar.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 552 B

BIN
electrum/gui/kivy/theming/light/action_button_group.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 B

BIN
electrum/gui/kivy/theming/light/action_group_dark.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 380 B

BIN
electrum/gui/kivy/theming/light/action_group_light.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 375 B

BIN
electrum/gui/kivy/theming/light/add_contact.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

BIN
electrum/gui/kivy/theming/light/arrow_back.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

BIN
electrum/gui/kivy/theming/light/bit_logo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 683 B

BIN
electrum/gui/kivy/theming/light/blue_bg_round_rb.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 B

BIN
electrum/gui/kivy/theming/light/btn_create_account.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 B

BIN
electrum/gui/kivy/theming/light/btn_create_act_disabled.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 427 B

BIN
electrum/gui/kivy/theming/light/btn_nfc.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

BIN
electrum/gui/kivy/theming/light/btn_send_address.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 B

BIN
electrum/gui/kivy/theming/light/btn_send_nfc.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 B

BIN
electrum/gui/kivy/theming/light/calculator.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

BIN
electrum/gui/kivy/theming/light/camera.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

BIN
electrum/gui/kivy/theming/light/card.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 866 B

BIN
electrum/gui/kivy/theming/light/card_bottom.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 383 B

BIN
electrum/gui/kivy/theming/light/card_btn.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 B

BIN
electrum/gui/kivy/theming/light/card_top.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 550 B

BIN
electrum/gui/kivy/theming/light/carousel_deselected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

BIN
electrum/gui/kivy/theming/light/carousel_selected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

BIN
electrum/gui/kivy/theming/light/clock1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

BIN
electrum/gui/kivy/theming/light/clock2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

BIN
electrum/gui/kivy/theming/light/clock3.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

BIN
electrum/gui/kivy/theming/light/clock4.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

BIN
electrum/gui/kivy/theming/light/clock5.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

BIN
electrum/gui/kivy/theming/light/close.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

BIN
electrum/gui/kivy/theming/light/closebutton.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

BIN
electrum/gui/kivy/theming/light/confirmed.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

BIN
electrum/gui/kivy/theming/light/contact.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

BIN
electrum/gui/kivy/theming/light/contact_overlay.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

BIN
electrum/gui/kivy/theming/light/copy.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 880 B

BIN
electrum/gui/kivy/theming/light/create_act_text.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 330 B

BIN
electrum/gui/kivy/theming/light/create_act_text_active.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 B

BIN
electrum/gui/kivy/theming/light/delete.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 453 B

BIN
electrum/gui/kivy/theming/light/dialog.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 B

BIN
electrum/gui/kivy/theming/light/dropdown_background.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

BIN
electrum/gui/kivy/theming/light/electrum_icon640.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

BIN
electrum/gui/kivy/theming/light/error.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

BIN
electrum/gui/kivy/theming/light/eye1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

BIN
electrum/gui/kivy/theming/light/gear.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

BIN
electrum/gui/kivy/theming/light/globe.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

BIN
electrum/gui/kivy/theming/light/icon_border.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 514 B

BIN
electrum/gui/kivy/theming/light/important.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

BIN
electrum/gui/kivy/theming/light/info.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

BIN
electrum/gui/kivy/theming/light/lightblue_bg_round_lb.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 B

BIN
electrum/gui/kivy/theming/light/lightning.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

6
electrum/gui/kivy/theming/light/lightning.svg

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="210.01" height="258.6" version="1.1" viewBox="0 0 55.564 68.421" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-37.066 -74.368)">
<path d="m38.127 110.58 40.719-34.163c1.8833-1.4037 4.6684-4.2048 2.3466 0.82819l-13.527 25.467 23.12 0.34508c1.0576 0.11762 2.8154-0.14879 1.1733 1.4493l-40.582 35.474c-2.6048 2.0742-6.2555 5.6722-2.6916-1.2423l13.251-25.398-22.913-0.55213c-2.1564 0.0996-2.6432-0.5521-0.8972-2.2085z" fill="#fff" fill-rule="evenodd" stroke-width=".13606"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 553 B

BIN
electrum/gui/kivy/theming/light/list.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

BIN
electrum/gui/kivy/theming/light/logo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

BIN
electrum/gui/kivy/theming/light/logo_atom_dull.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

BIN
electrum/gui/kivy/theming/light/mail_icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

BIN
electrum/gui/kivy/theming/light/manualentry.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

BIN
electrum/gui/kivy/theming/light/network.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

6
electrum/gui/kivy/theming/light/network.svg

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="512" height="512" version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<use transform="translate(-216 -252)" width="100%" height="100%" xlink:href="#path831"/>
<path id="path831" d="m244.94 256.71c-14.934 1.4562-25.469 19.091-24.913 37.987-2.155 55.246-0.0543 91.614 0.17146 143.49 0.95499 15.061 13.656 34.066 35.042 34.181 6.5619 0.30953 16.143 0.34717 30.995 0.34717 27.995 0 41.291 0.77865 41.291 2.4214 0 1.3327-2.9613 5.3827-6.5786 9-5.0074 5.0074-8.6562 6.5787-15.298 6.5787-10.341 0-14.124 3.1317-14.124 11.698v6.3018h144v-6.3018c0-8.5666-3.7834-11.698-14.124-11.698-6.6413 0-10.29-1.5713-15.297-6.5787-3.6174-3.6173-6.5786-7.6673-6.5786-9 0-1.6428 13.297-2.4214 41.291-2.4214 15.112-7e-3 63.701 6.378 66.081-35.293 0.62209-11.405 0.62843-32.056 0.62843-72.708 0-39.56-7e-3 -60.178-0.58008-71.767-0.6711-12.6-1.8798-36.301-24.834-36.233h-118.59zm10.586 36h216v144h-216v-72z" fill="#fff"/>
<path d="m110.41 469.11c-4.9443-1.8094-13.262-7.4832-18.485-12.608-15.863-15.568-16.401-19.038-16.401-105.69v-76.1h36v150.96l5.5228 5.5227c5.4382 5.4383 6.1188 5.5228 44.468 5.5228h38.945c3.4461 14.807 6.6856 24.504 14.871 36-30.131-0.82757-61.12 0.99452-90.818-1.2282-6.8699-0.53546-11.167-1.3039-14.104-2.3788z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

BIN
electrum/gui/kivy/theming/light/nfc.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

BIN
electrum/gui/kivy/theming/light/nfc_clock.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

BIN
electrum/gui/kivy/theming/light/nfc_phone.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

BIN
electrum/gui/kivy/theming/light/nfc_stage_one.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save