We now use the qml gui on Android, and haven't been maintaining the kivy GUI for a while.master
@ -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,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() |
||||
@ -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 |
|
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 |
|
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 |