remove the kivy gui
We now use the qml gui on Android, and haven't been maintaining the kivy GUI for a while.
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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
|
||||
|
Before Width: | Height: | Size: 40 KiB |
@@ -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
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
$HEADER$
|
||||
void main (void){
|
||||
gl_FragColor = frag_color * texture2D(texture0, tex_coord0);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 224 B |
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
Before Width: | Height: | Size: 71 KiB |
@@ -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]}}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.5 KiB |
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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')
|
||||
@@ -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()
|
||||
@@ -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')
|
||||
|
Before Width: | Height: | Size: 552 B |
|
Before Width: | Height: | Size: 188 B |
|
Before Width: | Height: | Size: 380 B |
|
Before Width: | Height: | Size: 375 B |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 683 B |
|
Before Width: | Height: | Size: 242 B |
|
Before Width: | Height: | Size: 311 B |
|
Before Width: | Height: | Size: 427 B |
|
Before Width: | Height: | Size: 362 B |
|
Before Width: | Height: | Size: 210 B |
|
Before Width: | Height: | Size: 209 B |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 866 B |
|
Before Width: | Height: | Size: 383 B |
|
Before Width: | Height: | Size: 357 B |
|
Before Width: | Height: | Size: 550 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 880 B |
|
Before Width: | Height: | Size: 330 B |
|
Before Width: | Height: | Size: 308 B |
|
Before Width: | Height: | Size: 453 B |
|
Before Width: | Height: | Size: 393 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 514 B |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 244 B |
|
Before Width: | Height: | Size: 4.7 KiB |
@@ -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 |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
@@ -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 |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 13 KiB |