From dc807746cd0aa999680596670b62ed7a850c44eb Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sat, 11 Apr 2015 08:43:43 +0200 Subject: [PATCH 01/22] vibrator for android v < 4.0, pep257 and flake8 --- examples/vibrator/main.py | 12 ++++----- plyer/facades.py | 2 +- plyer/platforms/android/vibrator.py | 38 +++++++++++++++++++++-------- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/examples/vibrator/main.py b/examples/vibrator/main.py index 283f12c85..c6e14b782 100644 --- a/examples/vibrator/main.py +++ b/examples/vibrator/main.py @@ -1,11 +1,7 @@ - +"""Plyer Vibrator Example.""" from kivy.app import App -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.button import Button from kivy.lang import Builder -from kivy.properties import StringProperty, BooleanProperty - -from plyer import vibrator +from kivy.uix.boxlayout import BoxLayout Builder.load_string(''' #:import vibrator plyer.vibrator @@ -39,11 +35,15 @@ class VibrationInterface(BoxLayout): + """Root Widget.""" pass class VibrationApp(App): + """Main Application.""" + def build(self): + """Return root widget.""" return VibrationInterface() def on_pause(self): diff --git a/plyer/facades.py b/plyer/facades.py index 10ec41038..e8a948531 100644 --- a/plyer/facades.py +++ b/plyer/facades.py @@ -251,7 +251,7 @@ def vibrate(self, time=1): def _vibrate(self, **kwargs): raise NotImplementedError() - def pattern(self, pattern=[0, 1], repeat=-1): + def pattern(self, pattern=(0, 1), repeat=-1): '''Ask the vibrator to vibrate with the given pattern, with an optional repeat. diff --git a/plyer/platforms/android/vibrator.py b/plyer/platforms/android/vibrator.py index 87ff794f5..7aece2e76 100644 --- a/plyer/platforms/android/vibrator.py +++ b/plyer/platforms/android/vibrator.py @@ -1,6 +1,8 @@ -from jnius import autoclass, cast +"""Implementation Vibrator for Android.""" +from jnius import autoclass from plyer.facades import Vibrator from plyer.platforms.android import activity +from plyer.platforms.android import SDK_INT Intent = autoclass('android.content.Intent') Context = autoclass('android.content.Context') @@ -8,27 +10,43 @@ class AndroidVibrator(Vibrator): - def _vibrate(self, **kwargs): - time = kwargs.get('time') + """Android Vibrator class. + Supported features: + * vibrate for some period of time. + * vibrate from given pattern. + * cancel vibration. + * check whether Vibrator exists. + + .. warning:: + Feature check if Vibrator exists works only for + Android ver. 3.0.x (SDK >= 11) and above. For android with SDK < 11 + it just returns `None`. + + """ + + def _vibrate(self, time=1): if vibrator: vibrator.vibrate(int(1000 * time)) - def _pattern(self, **kwargs): - pattern = kwargs.get('pattern') - repeat = kwargs.get('repeat') - + def _pattern(self, pattern=(0, 1), repeat=-1): pattern = [int(1000 * time) for time in pattern] if vibrator: vibrator.vibrate(pattern, repeat) - def _exists(self, **kwargs): - return vibrator.hasVibrator() + def _exists(self): + if SDK_INT >= 11: + return vibrator.hasVibrator() + return - def _cancel(self, **kwargs): + def _cancel(self): vibrator.cancel() def instance(): + """Returns Vibrator with android features. + + :return: instance of class AndroidVibrator + """ return AndroidVibrator() From e382c3c5cbad8ca759a26b2b10ec616f745633c7 Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sat, 11 Apr 2015 09:59:44 +0200 Subject: [PATCH 02/22] updated document --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a02ec19a7..8498806ba 100644 --- a/README.rst +++ b/README.rst @@ -26,7 +26,7 @@ GPS X X X Notifications X X X X X Text to speech X X X X X X Email (open mail client) X X X X X -Vibrator X +Vibrator X X Sms (send messages) X X Compass X X X Unique ID (IMEI or SN) X X X X X X From 734e5e98fe3f202fcb5275c9770fc9879c67fbfc Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Fri, 1 May 2015 14:09:36 +0200 Subject: [PATCH 03/22] added first structure --- examples/tests/buildozer.spec | 189 +++++++++++++++++++++++++++ examples/tests/main.py | 83 ++++++++++++ examples/tests/recycleview.py | 237 ++++++++++++++++++++++++++++++++++ 3 files changed, 509 insertions(+) create mode 100644 examples/tests/buildozer.spec create mode 100644 examples/tests/main.py create mode 100644 examples/tests/recycleview.py diff --git a/examples/tests/buildozer.spec b/examples/tests/buildozer.spec new file mode 100644 index 000000000..f959e7470 --- /dev/null +++ b/examples/tests/buildozer.spec @@ -0,0 +1,189 @@ +[app] + +# (str) Title of your application +title = Plyer Tests + +# (str) Package name +package.name = plyer.test + +# (str) Package domain (needed for android/ios packaging) +package.domain = org.test + +# (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 + +# (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 = tests, bin + +# (list) List of exclusions using pattern matching +#source.exclude_patterns = license,images/*/*.jpg + +# (str) Application versioning (method 1) +# version.regex = __version__ = ['"](.*)['"] +# version.filename = %(source.dir)s/main.py + +# (str) Application versioning (method 2) +version = 0.1 + +# (list) Application requirements +# comma seperated e.g. requirements = sqlite3,kivy +requirements = kivy,plyer + +# (list) Garden requirements +#garden_requirements = + +# (str) Presplash of the application +#presplash.filename = %(source.dir)s/data/presplash.png + +# (str) Icon of the application +#icon.filename = %(source.dir)s/data/icon.png + +# (str) Supported orientation (one of landscape, portrait or all) +orientation = portrait + +# (bool) Indicate if the application should be fullscreen or not +fullscreen = 0 + + +# +# Android specific +# + +# (list) Permissions +#android.permissions = INTERNET + +# (int) Android API to use +#android.api = 14 + +# (int) Minimum API required (8 = Android 2.2 devices) +#android.minapi = 8 + +# (int) Android SDK version to use +#android.sdk = 21 + +# (str) Android NDK version to use +#android.ndk = 9c + +# (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 = + +# (str) Android SDK directory (if empty, it will be automatically downloaded.) +#android.sdk_path = + +# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) +#android.p4a_dir = + +# (list) python-for-android whitelist +#android.p4a_whitelist = + +# (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 + +# (list) List of Java files to add to the android project (can be java or a +# directory containing the files) +#android.add_src = + +# (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 tag +#android.manifest.intent_filters = + +# (list) Android additionnal libraries to copy into libs/armeabi +#android.add_libs_armeabi = libs/android/*.so +#android.add_libs_armeabi_v7a = libs/android-v7/*.so +#android.add_libs_x86 = libs/android-x86/*.so +#android.add_libs_mips = libs/android-mips/*.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 + +# (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 = + +# +# 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: ()" + +# (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 + +# (int) Display warning if buildozer is run as root (0 = False, 1 = True) +warn_on_root = 1 + + +# ----------------------------------------------------------------------------- +# 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 diff --git a/examples/tests/main.py b/examples/tests/main.py new file mode 100644 index 000000000..e7560730e --- /dev/null +++ b/examples/tests/main.py @@ -0,0 +1,83 @@ +from recycleview import RecycleView +from kivy.base import runTouchApp +from kivy.lang import Builder +from kivy.clock import Clock +from kivy.properties import ListProperty +from kivy.app import App +from kivy.metrics import sp +import random + +Builder.load_string(""" +: + canvas.before: + Color: + rgb: (.5, .5, .5) + Rectangle: + pos: self.pos + size: self.size + +: + index: 0 + spacing: "5dp" + contact_name: "" + canvas.before: + Color: + rgb: (0, 0, 0) + Rectangle: + pos: self.pos + size: self.size + Label: + font_size: "14sp" + text: root.contact_name + color: (0, 1, 0, 1) + text_size: (self.width, None) +""") + +class TestApp(App): + + contacts = ListProperty() + rv = RecycleView() + + def add_(self): + + names = ["Robert", "George", "Joseph", "Donald", "Mark", "Anthony", "Gary"] + + def callback(dt): + self.rv.data.append({ + "viewclass": "LogItem", + "contact_name": "{} {}".format( + random.choice(names), + random.choice(names) + ) + }) + self.rv.refresh_from_data(force=True) + Clock.schedule_interval(callback, 1 / 10.) + + + + def build(self): + # Create a data set + names = ["Robert", "George", "Joseph", "Donald", "Mark", "Anthony", "Gary"] + + for x in range(10): + if x % 5 == 0: + self.contacts.append({ + "viewclass": "Separator", + "height": sp(20) + }) + self.contacts.append({ + "index": x, + "viewclass": "LogItem", + "contact_name": "{} {}".format( + random.choice(names), + random.choice(names) + ) + }) + + self.rv.key_viewclass = "viewclass" + self.rv.key_height = "height" + self.rv.data = self.contacts + self.add_() + return self.rv + +TestApp().run() diff --git a/examples/tests/recycleview.py b/examples/tests/recycleview.py new file mode 100644 index 000000000..3f80eb03b --- /dev/null +++ b/examples/tests/recycleview.py @@ -0,0 +1,237 @@ +""" +RecycleView +=========== + +Data accepted: list of dict. + +TODO: + - recycle old widgets based on the class + - add custom function to get view height + - add custom function to get view class + - update view size when created + - move all internals to adapter + - selection +""" + +from kivy.compat import string_types +from kivy.uix.relativelayout import RelativeLayout +from kivy.lang import Builder +from kivy.properties import NumericProperty, AliasProperty, StringProperty, \ + ObjectProperty +from kivy.factory import Factory +from kivy.clock import Clock + +Builder.load_string(""" +: + ScrollView: + id: sv + do_scroll_x: False + on_scroll_y: root.refresh_from_data() + RecycleViewLayout: + id: layout + size_hint: None, None + size: root.width, root.computed_height +""") + + +class RecycleViewLayout(RelativeLayout): + pass + + +class RecycleView(RelativeLayout): + + data = ObjectProperty() + adapter = ObjectProperty() + + default_height = NumericProperty("48dp") + key_height = StringProperty() + viewclass = ObjectProperty() + key_viewclass = StringProperty() + + # internals + computed_height = NumericProperty(0) + computed_heights = [] + computed_positions = [] + views = {} + dirty_views = {} + + def on_viewclass(self, instance, value): + if isinstance(value, string_types): + self.viewclass = getattr(Factory, value) + + def do_layout(self, *args): + super(RecycleView, self).do_layout(*args) + self.refresh_from_data(True) + + def make_view_dirty(self, view, index): + viewclass = view.__class__ + if viewclass not in self.dirty_views: + self.dirty_views[viewclass] = {index: view} + else: + self.dirty_views[viewclass][index] = view + + def refresh_from_data(self, force=False): + """The data has changed, update the RecycleView internals + """ + if force: + for index, view in self.views.items(): + self.make_view_dirty(view, index) + self.views = {} + self.compute_views_heights() + self.compute_visible_views() + + def compute_views_heights(self): + """(internal) Calculate all the views height according to + default_height, key_height, and then calculate their future positions + """ + height = 0 + key_height = self.key_height + default_height = self.default_height + self.computed_heights = [ + item.get(key_height, default_height) + for item in self.data + ] + self.computed_height = sum(self.computed_heights) + self.computed_positions = list( + self._compute_positions(self.computed_heights)) + + def _compute_positions(self, heights): + y = 0 + for height in heights: + yield y + y += height + + def compute_visible_views(self): + """(internal) Determine the views that need to be showed in the current + scrollview. All the hidden views will be flagged as dirty, and might + be resued for others views. + """ + # determine the view to create for the scrollview y / height + sv = self.ids.sv + layout = self.ids.layout + scroll_y = 1 - (min(1, max(sv.scroll_y, 0))) + px_start = (layout.height - self.height) * scroll_y + px_end = px_start + self.height + + # now calculate the view indices we must show + i_start = self.get_view_index_at(px_start) + i_end = self.get_view_index_at(px_end) + + current_views = self.views + visible_views = {} + dirty_views = self.dirty_views + + # iterate though the visible view + # add them into the layout if not already done + for index in range(i_start, i_end + 1): + view = self.get_view(index) + if not view: + continue + + visible_views[index] = view + current_views.pop(index, None) + + # add to the layout if it's not already done + if view.parent: + continue + layout.add_widget(view) + + # put all the hidden view as dirty views + for index, view in current_views.items(): + layout.remove_widget(view) + self.make_view_dirty(view, index) + + # save the current visible views + self.views = visible_views + + def get_view(self, index): + """Return a view instance for the `index` + """ + if index in self.views: + return self.views[index] + + dirty_views = self.dirty_views + viewclass = self.get_viewclass(index) + if viewclass in dirty_views: + + # we found ourself in the dirty list, no need to update data! + if index in dirty_views[viewclass]: + view = dirty_views[viewclass].pop(index) + self.refresh_view_layout(view, index) + self.views[index] = view + return view + + # we are not in the dirty list, just take one and reuse it. + if dirty_views[viewclass]: + previous_index = dirty_views[viewclass].keys()[-1] + view = dirty_views[viewclass].pop(previous_index) + # update view data + item = self.data[index] + for key, value in item.items(): + setattr(view, key, value) + self.refresh_view_layout(view, index) + self.views[index] = view + return view + + # create a fresh one + self.views[index] = view = self.create_view(index) + self.refresh_view_layout(view, index) + return view + + def refresh_view_layout(self, view, index): + """(internal) Refresh the layout of a view. Size and pos are determine + by the `RecycleView` according to the view `index` informations + """ + view.size_hint = None, None + view.width = self.width + view.height = h = self.computed_heights[index] + view.y = self.computed_height - self.computed_positions[index] - h + + def create_view(self, index): + """Create the view for the `index` + """ + viewclass = self.get_viewclass(index) + item = self.data[index] + # FIXME: we could pass the data though the constructor, but that wont + # work for kv-declared classes, and might lead the user to think it can + # work for reloading as well. + view = viewclass(**item) + for key, value in item.items(): + setattr(view, key, value) + return view + + def get_view_position(self, index): + """Get the position for the view at `index` + """ + return self.computed_positions[index] + + def get_view_height(self, index): + """Get the height for the view at `index` + """ + return self.computed_heights[index] + + def get_viewclass(self, index): + """Get the class needed to create the view `index` + """ + viewclass = None + if self.key_viewclass: + viewclass = self.data[index].get(self.key_viewclass) + viewclass = getattr(Factory, viewclass) + if not viewclass: + viewclass = self.viewclass + return viewclass + + def get_view_index_at(self, y): + """Return the view `index` for the `y` position + """ + for index, pos in enumerate(self.computed_positions): + if pos > y: + return index - 1 + return index + + def on_data(self, instance, value): + # data changed, right now, remove all the widgets. + self.dirty_views = {} + self.views = {} + self.ids.layout.clear_widgets() + self._trigger_layout() From aa06dbbe969411bc5aaa4ab12f6724e83629b4cd Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sun, 3 May 2015 10:01:39 +0200 Subject: [PATCH 04/22] as --- examples/tests/main.py | 137 +++++++++++++++++++++++++++++------------ 1 file changed, 98 insertions(+), 39 deletions(-) diff --git a/examples/tests/main.py b/examples/tests/main.py index e7560730e..5e7f76340 100644 --- a/examples/tests/main.py +++ b/examples/tests/main.py @@ -1,25 +1,34 @@ +from collections import defaultdict from recycleview import RecycleView from kivy.base import runTouchApp from kivy.lang import Builder from kivy.clock import Clock +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.button import Button from kivy.properties import ListProperty from kivy.app import App from kivy.metrics import sp import random - + Builder.load_string(""" -: +: + module_name: "" canvas.before: Color: rgb: (.5, .5, .5) Rectangle: pos: self.pos size: self.size + Label: + font_size: "14sp" + color: (0, 1, 0, 1) + text_size: (self.width, None) + text: root.module_name : index: 0 spacing: "5dp" - contact_name: "" + test_result: "" canvas.before: Color: rgb: (0, 0, 0) @@ -28,56 +37,106 @@ size: self.size Label: font_size: "14sp" - text: root.contact_name + text: root.test_result color: (0, 1, 0, 1) text_size: (self.width, None) """) - + +registered_modules = defaultdict(list) + + +def register_test(function_name): + def decorator(f): + module_name = function_name.split('.')[1] + registered_modules[module_name].append((function_name, f)) + return f + return decorator + + +@register_test('plyer.vibrator.vibrate()') +def test_vibrator_vibrate(): + from plyer import vibrator + vibrator.vibrate() + + +@register_test('plyer.vibrator.vibrate(2s)') +def test_vibrator_vibrate(): + from plyer import vibrator + vibrator.vibrate(2) + + +@register_test('plyer.vibrator.cancel()') +def test_vibrator_vibrate(): + from plyer import vibrator + vibrator.cancel() + + +@register_test('plyer.vibrator.pattern([0.5, 0.1, 0.4])') +def test_vibrator_vibrate(): + from plyer import vibrator + vibrator.pattern([0.5, 0.1, 0.4]) + + class TestApp(App): - contacts = ListProperty() + results = ListProperty() rv = RecycleView() - def add_(self): + def try_method(self, f): + try: + f() + return 0 + except ImportError: + print 'cannot import' + return -3 + except NotImplementedError: + print 'Not implemented yet on this platform' + return -2 + except Exception: + print 'error' + return -1 - names = ["Robert", "George", "Joseph", "Donald", "Mark", "Anthony", "Gary"] + def run_tests(self): - def callback(dt): - self.rv.data.append({ - "viewclass": "LogItem", - "contact_name": "{} {}".format( - random.choice(names), - random.choice(names) - ) - }) - self.rv.refresh_from_data(force=True) - Clock.schedule_interval(callback, 1 / 10.) + for module, functions in registered_modules.iteritems(): + print 'testing module', module + self._add_separator(module) + for name, function in functions: + print 'testing function', name + result = self.try_method(function) + self._add_test_result(name, result) + def _add_separator(self, module_name): + self.results.append({ + "viewclass": "Separator", + "height": sp(20), + "module_name": module_name + }) + self.rv.refresh_from_data(force=True) - def build(self): - # Create a data set - names = ["Robert", "George", "Joseph", "Donald", "Mark", "Anthony", "Gary"] - - for x in range(10): - if x % 5 == 0: - self.contacts.append({ - "viewclass": "Separator", - "height": sp(20) - }) - self.contacts.append({ - "index": x, - "viewclass": "LogItem", - "contact_name": "{} {}".format( - random.choice(names), - random.choice(names) - ) - }) + def _add_test_result(self, test_name, result): + self.rv.data.append({ + "viewclass": "LogItem", + "test_result": "{} {}".format( + result, + test_name, + ) + }) + self.rv.refresh_from_data(force=True) + def build(self): self.rv.key_viewclass = "viewclass" self.rv.key_height = "height" - self.rv.data = self.contacts - self.add_() + self.rv.data = self.results + + layout = BoxLayout(orientation='vertical') + refresh_button = Button(text='Run Tests', size_hint=(1, .1)) + refresh_button.bind(on_press=self.run_tests()) + layout.add_widget(self.rv) + return self.rv -TestApp().run() +test_app = TestApp() +test_app.run() +test_app.run_tests() From 20cd77da61c49352fce9ba3e21493a4edc9a1bbb Mon Sep 17 00:00:00 2001 From: albericc Date: Mon, 26 Jan 2015 15:33:35 +0100 Subject: [PATCH 05/22] responds to issue 109 https://github.com/kivy/plyer/issues/109 --- plyer/utils.py | 82 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 29 deletions(-) diff --git a/plyer/utils.py b/plyer/utils.py index 9547edcdb..5b8579e74 100644 --- a/plyer/utils.py +++ b/plyer/utils.py @@ -14,34 +14,58 @@ _platform_android = None -def _determine_platform(): - global _platform_ios, _platform_android - - if _platform_android is None: - # ANDROID_ARGUMENT and ANDROID_PRIVATE are 2 environment variables from - # python-for-android project - _platform_android = 'ANDROID_ARGUMENT' in environ - - if _platform_ios is None: - _platform_ios = (environ.get('KIVY_BUILD', '') == 'ios') - - # On android, _sys_platform return 'linux2', so prefer to check the import - # of Android module than trying to rely on _sys_platform. - if _platform_android is True: - return 'android' - elif _platform_ios is True: - return 'ios' - elif _sys_platform in ('win32', 'cygwin'): - return 'win' - elif _sys_platform in ('darwin', ): - return 'macosx' - elif _sys_platform.startswith('linux'): - return 'linux' - return 'unknown' - -#: Return the version of the current platform, one of win, linux, android, -#: macosx, ios, unknown -platform = _determine_platform() +class Platform(object): + # refactored to class to allow module function to be replaced + # with module variable + + def __init__(self): + self._platform_ios = None + self._platform_android = None + + def __eq__(self, other): + return other == self._get_platform() + + def __ne__(self, other): + return other != self._get_platform() + + def __str__(self): + return self._get_platform() + + def __repr__(self): + return 'platform name: \'{platform}\' from: \n{instance}'.format( + platform=self._get_platform(), + instance=super(Platform, self).__repr__() + ) + + def __hash__(self): + return self._get_platform().__hash__() + + def _get_platform(self): + + if self._platform_android is None: + # ANDROID_ARGUMENT and ANDROID_PRIVATE are 2 environment variables + # from python-for-android project + self._platform_android = 'ANDROID_ARGUMENT' in environ + + if self._platform_ios is None: + self._platform_ios = (environ.get('KIVY_BUILD', '') == 'ios') + + # On android, _sys_platform return 'linux2', so prefer to check the + # import of Android module than trying to rely on _sys_platform. + if self._platform_android is True: + return 'android' + elif self._platform_ios is True: + return 'ios' + elif _sys_platform in ('win32', 'cygwin'): + return 'win' + elif _sys_platform == 'darwin': + return 'macosx' + elif _sys_platform[:5] == 'linux': + return 'linux' + return 'unknown' + + +platform = Platform() class Proxy(object): @@ -106,7 +130,7 @@ def whereis_exe(program): ''' Tries to find the program on the system path. Returns the path if it is found or None if it's not found. ''' - path_split = ';' if _determine_platform() == 'win' else ':' + path_split = ';' if platform == 'win' else ':' for p in environ.get('PATH', '').split(path_split): if path.exists(path.join(p, program)) and \ not path.isdir(path.join(p, program)): From bc76a702dfa137299b6926445180de4472b3cde6 Mon Sep 17 00:00:00 2001 From: laltin Date: Wed, 11 Mar 2015 17:40:33 +0200 Subject: [PATCH 06/22] make pep8 compatible --- plyer/platforms/ios/gps.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plyer/platforms/ios/gps.py b/plyer/platforms/ios/gps.py index 2262d1262..7e38c8c4c 100644 --- a/plyer/platforms/ios/gps.py +++ b/plyer/platforms/ios/gps.py @@ -11,7 +11,7 @@ CLLocationManager = autoclass('CLLocationManager') -class iOSGPS(GPS): +class IosGPS(GPS): def _configure(self): if not hasattr(self, '_location_manager'): self._location_manager = CLLocationManager.alloc().init() @@ -20,26 +20,26 @@ def _start(self): self._location_manager.delegate = self self._location_manager.requestWhenInUseAuthorization() - # NSLocationWhenInUseUsageDescription key must exist in Info.plist - # file. When the authorization prompt is displayed your app goes - # into pause mode and if your app doesn't support background mode - # it will crash. + # NSLocationWhenInUseUsageDescription key must exist in Info.plist + # file. When the authorization prompt is displayed your app goes + # into pause mode and if your app doesn't support background mode + # it will crash. self._location_manager.startUpdatingLocation() def _stop(self): self._location_manager.stopUpdatingLocation() - + @protocol('CLLocationManagerDelegate') def locationManager_didUpdateLocations_(self, manager, locations): location = manager.location - + self.on_location( lat=location.coordinate.a, lon=location.coordinate.b, speed=location.speed, - bearing=location.course, # TODO: check if bearing and course is same + bearing=location.course, altitude=location.altitude) def instance(): - return iOSGPS() + return IosGPS() From a74649ea302c8a76f0c6bad5b5a52b3f25a55d61 Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sat, 14 Mar 2015 09:29:05 +0100 Subject: [PATCH 07/22] pep8 - removed unused imports and variables --- plyer/platforms/android/vibrator.py | 2 +- plyer/platforms/ios/email.py | 1 - plyer/platforms/linux/email.py | 1 - plyer/platforms/linux/notification.py | 2 +- plyer/platforms/macosx/email.py | 1 - plyer/platforms/win/email.py | 1 - plyer/platforms/win/libs/balloontip.py | 7 +------ plyer/platforms/win/libs/win_api_defs.py | 7 +++---- 8 files changed, 6 insertions(+), 16 deletions(-) diff --git a/plyer/platforms/android/vibrator.py b/plyer/platforms/android/vibrator.py index 87ff794f5..c26b05a33 100644 --- a/plyer/platforms/android/vibrator.py +++ b/plyer/platforms/android/vibrator.py @@ -1,4 +1,4 @@ -from jnius import autoclass, cast +from jnius import autoclass from plyer.facades import Vibrator from plyer.platforms.android import activity diff --git a/plyer/platforms/ios/email.py b/plyer/platforms/ios/email.py index 11fa1dd7f..7e55e4eda 100644 --- a/plyer/platforms/ios/email.py +++ b/plyer/platforms/ios/email.py @@ -4,7 +4,6 @@ from urllib import quote from plyer.facades import Email -from plyer.utils import whereis_exe from pyobjus import autoclass, objc_str from pyobjus.dylib_manager import load_framework diff --git a/plyer/platforms/linux/email.py b/plyer/platforms/linux/email.py index 0b30dd74f..ceb54979f 100644 --- a/plyer/platforms/linux/email.py +++ b/plyer/platforms/linux/email.py @@ -12,7 +12,6 @@ def _send(self, **kwargs): recipient = kwargs.get('recipient') subject = kwargs.get('subject') text = kwargs.get('text') - create_chooser = kwargs.get('create_chooser') uri = "mailto:" if recipient: diff --git a/plyer/platforms/linux/notification.py b/plyer/platforms/linux/notification.py index 75d02bba6..d78f13000 100644 --- a/plyer/platforms/linux/notification.py +++ b/plyer/platforms/linux/notification.py @@ -29,8 +29,8 @@ def _notify(self, **kwargs): _bus_name = 'org.freedesktop.Notifications' _object_path = '/org/freedesktop/Notifications' _interface_name = _bus_name - import dbus + import dbus session_bus = dbus.SessionBus() obj = session_bus.get_object(_bus_name, _object_path) interface = dbus.Interface(obj, _interface_name) diff --git a/plyer/platforms/macosx/email.py b/plyer/platforms/macosx/email.py index 85ebba1c8..8e29fa905 100644 --- a/plyer/platforms/macosx/email.py +++ b/plyer/platforms/macosx/email.py @@ -14,7 +14,6 @@ def _send(self, **kwargs): recipient = kwargs.get('recipient') subject = kwargs.get('subject') text = kwargs.get('text') - create_chooser = kwargs.get('create_chooser') uri = "mailto:" if recipient: diff --git a/plyer/platforms/win/email.py b/plyer/platforms/win/email.py index 5211ff400..e33f5cf7b 100644 --- a/plyer/platforms/win/email.py +++ b/plyer/platforms/win/email.py @@ -11,7 +11,6 @@ def _send(self, **kwargs): recipient = kwargs.get('recipient') subject = kwargs.get('subject') text = kwargs.get('text') - create_chooser = kwargs.get('create_chooser') uri = "mailto:" if recipient: diff --git a/plyer/platforms/win/libs/balloontip.py b/plyer/platforms/win/libs/balloontip.py index cbe4135d5..171b25f2b 100644 --- a/plyer/platforms/win/libs/balloontip.py +++ b/plyer/platforms/win/libs/balloontip.py @@ -142,9 +142,4 @@ def remove_notify(self): def balloon_tip(**kwargs): - title = kwargs.get('title', '') - message = kwargs.get('message', '') - app_name = kwargs.get('app_name', '') - app_icon = kwargs.get('app_icon', '') - timeout = kwargs.get('timeout', 10) - w = WindowsBalloonTip(**kwargs) + WindowsBalloonTip(**kwargs) diff --git a/plyer/platforms/win/libs/win_api_defs.py b/plyer/platforms/win/libs/win_api_defs.py index 8727bfd82..7aed430a8 100644 --- a/plyer/platforms/win/libs/win_api_defs.py +++ b/plyer/platforms/win/libs/win_api_defs.py @@ -9,11 +9,10 @@ 'DestroyWindow', 'LoadIconW') import ctypes -from ctypes import (Structure, windll, sizeof, byref, POINTER, memset, - WINFUNCTYPE) +from ctypes import Structure, windll, sizeof, POINTER, WINFUNCTYPE from ctypes.wintypes import (DWORD, HICON, HWND, UINT, WCHAR, WORD, BYTE, - LPCWSTR, LPWSTR, INT, LPVOID, HINSTANCE, HMENU, LPARAM, WPARAM, - HBRUSH, HMODULE, ATOM, BOOL, HANDLE, LONG, HHOOK) + LPCWSTR, INT, LPVOID, HINSTANCE, HMENU, LPARAM, WPARAM, + HBRUSH, HMODULE, ATOM, BOOL, HANDLE) LRESULT = LPARAM HRESULT = HANDLE HCURSOR = HICON From 860bc729b611effe5b3cf186522b7ad64a746fc1 Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sun, 15 Mar 2015 06:47:42 +0100 Subject: [PATCH 08/22] removed unused imports and refactor --- examples/vibrator/main.py | 3 --- plyer/platforms/android/vibrator.py | 10 ++-------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/examples/vibrator/main.py b/examples/vibrator/main.py index 283f12c85..3644b4e93 100644 --- a/examples/vibrator/main.py +++ b/examples/vibrator/main.py @@ -1,11 +1,8 @@ from kivy.app import App from kivy.uix.boxlayout import BoxLayout -from kivy.uix.button import Button from kivy.lang import Builder -from kivy.properties import StringProperty, BooleanProperty -from plyer import vibrator Builder.load_string(''' #:import vibrator plyer.vibrator diff --git a/plyer/platforms/android/vibrator.py b/plyer/platforms/android/vibrator.py index c26b05a33..4642957ea 100644 --- a/plyer/platforms/android/vibrator.py +++ b/plyer/platforms/android/vibrator.py @@ -2,22 +2,16 @@ from plyer.facades import Vibrator from plyer.platforms.android import activity -Intent = autoclass('android.content.Intent') Context = autoclass('android.content.Context') vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE) class AndroidVibrator(Vibrator): - def _vibrate(self, **kwargs): - time = kwargs.get('time') - + def _vibrate(self, time=None, **kwargs): if vibrator: vibrator.vibrate(int(1000 * time)) - def _pattern(self, **kwargs): - pattern = kwargs.get('pattern') - repeat = kwargs.get('repeat') - + def _pattern(self, pattern=None, repeat=None, **kwargs): pattern = [int(1000 * time) for time in pattern] if vibrator: From 4c7d9190c09541bdf8a2e9841413d129cf8a9e33 Mon Sep 17 00:00:00 2001 From: Robert Jerovsek Date: Sun, 15 Mar 2015 19:44:34 +0900 Subject: [PATCH 09/22] PEP8 and typo fixes in MacOS X file chooser. --- plyer/platforms/macosx/filechooser.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plyer/platforms/macosx/filechooser.py b/plyer/platforms/macosx/filechooser.py index 520d32711..5166fa489 100644 --- a/plyer/platforms/macosx/filechooser.py +++ b/plyer/platforms/macosx/filechooser.py @@ -13,6 +13,7 @@ NSSavePanel = autoclass('NSSavePanel') NSOKButton = 1 + class MacFileChooser(object): """A native implementation of file chooser dialogs using Apple's API through pyobjus. @@ -63,7 +64,7 @@ def run(self): # Mac OS X does not support wildcards unlike the other platforms. # This tries to convert wildcards to "extensions" when possible, # ans sets the panel to also allow other file types, just to be safe. - if len(self.filters) > 0 + if len(self.filters) > 0: filthies = [] for f in self.filters: if type(f) == str: @@ -81,7 +82,7 @@ def run(self): filthies.append(objc_str(pystr)) ftypes_arr = objc_arr(filthies) - panel.setAllowedFileTypes_(ftypes) + panel.setAllowedFileTypes_(ftypes_arr) panel.setAllowsOtherFileTypes_(not self.use_extensions) if self.path: @@ -102,5 +103,6 @@ class MacOSXFileChooser(FileChooser): def _file_selection_dialog(self, **kwargs): return MacFileChooser(**kwargs).run() + def instance(): return MacOSXFileChooser() From 2c3031dbe5ae82cc159b3e9b7c224d2f61019ad7 Mon Sep 17 00:00:00 2001 From: thegrymek Date: Thu, 30 Apr 2015 15:34:49 +0200 Subject: [PATCH 10/22] remove unused variables --- plyer/utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/plyer/utils.py b/plyer/utils.py index 5b8579e74..347c8d526 100644 --- a/plyer/utils.py +++ b/plyer/utils.py @@ -10,9 +10,6 @@ from os import path from sys import platform as _sys_platform -_platform_ios = None -_platform_android = None - class Platform(object): # refactored to class to allow module function to be replaced From 69671c0b3439fef0c7581266be91fba96d443dfa Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sun, 3 May 2015 13:48:46 +0200 Subject: [PATCH 11/22] merge --- examples/tests/main.py | 102 +++++++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 34 deletions(-) diff --git a/examples/tests/main.py b/examples/tests/main.py index 5e7f76340..d4eb42a28 100644 --- a/examples/tests/main.py +++ b/examples/tests/main.py @@ -9,8 +9,13 @@ from kivy.app import App from kivy.metrics import sp import random +from plyer.utils import platform -Builder.load_string(""" +import logging + +log = logging.getLogger() + +kv = """ : module_name: "" canvas.before: @@ -26,7 +31,6 @@ text: root.module_name : - index: 0 spacing: "5dp" test_result: "" canvas.before: @@ -40,7 +44,28 @@ text: root.test_result color: (0, 1, 0, 1) text_size: (self.width, None) -""") + +# app example +BoxLayout: + orientation: "vertical" + BoxLayout: + padding: "2sp" + spacing: "2sp" + size_hint_y: None + height: "48sp" + + Button: + text: "Sort data" + on_release: app.sort_data() + + Button: + text: "Generate new data" + on_release: app.generate_new_data() + + RecycleView: + id: rv + +""" registered_modules = defaultdict(list) @@ -77,66 +102,75 @@ def test_vibrator_vibrate(): vibrator.pattern([0.5, 0.1, 0.4]) -class TestApp(App): +class State: + + Ok = 'Ok' + NotImplemented = 'Not Implemented' + Error = 'Error' + + +class TestViewApp(App): results = ListProperty() rv = RecycleView() + def build(self): + self.root = Builder.load_string(kv) + self.rv = self.root.ids.rv + self.rv.key_viewclass = "viewclass" + self.rv.key_size = "height" + self.rv.data = self.results + self.run_tests() + def try_method(self, f): try: f() - return 0 - except ImportError: - print 'cannot import' - return -3 + log.info(State.Ok) + return State.Ok except NotImplementedError: - print 'Not implemented yet on this platform' - return -2 + log.info(State.NotImplemented) + return State.NotImplemented except Exception: - print 'error' - return -1 + log.info(State.Error) + return State.Error def run_tests(self): for module, functions in registered_modules.iteritems(): - print 'testing module', module - self._add_separator(module) + log.info('Testing module %s', module) + try: + __import__('plyer.platforms.%s.%s' % (platform, module)) + except ImportError: + self._add_separator(module, State.NotImplemented) + + total = len(functions) + passed = 0 for name, function in functions: - print 'testing function', name + log.info('Testing function %s', name) result = self.try_method(function) - + if result == State.Ok: + passed += 1 self._add_test_result(name, result) - def _add_separator(self, module_name): + def _add_separator(self, module_name, state=None): self.results.append({ "viewclass": "Separator", "height": sp(20), - "module_name": module_name + "index": len(self.results), + "module_name": '%25s %s' % (module_name, state) }) self.rv.refresh_from_data(force=True) - def _add_test_result(self, test_name, result): + def _add_test_result(self, test_name, state): self.rv.data.append({ "viewclass": "LogItem", + "index": len(self.results), "test_result": "{} {}".format( - result, + state, test_name, ) }) self.rv.refresh_from_data(force=True) - def build(self): - self.rv.key_viewclass = "viewclass" - self.rv.key_height = "height" - self.rv.data = self.results - - layout = BoxLayout(orientation='vertical') - refresh_button = Button(text='Run Tests', size_hint=(1, .1)) - refresh_button.bind(on_press=self.run_tests()) - layout.add_widget(self.rv) - - return self.rv +TestViewApp().run() -test_app = TestApp() -test_app.run() -test_app.run_tests() From 4da047e223af2fd10cfe22954cbce8309c6bb5ad Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sun, 3 May 2015 14:30:35 +0200 Subject: [PATCH 12/22] conflicts main --- plyer/platforms/android/vibrator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plyer/platforms/android/vibrator.py b/plyer/platforms/android/vibrator.py index 7aece2e76..78177c997 100644 --- a/plyer/platforms/android/vibrator.py +++ b/plyer/platforms/android/vibrator.py @@ -1,4 +1,5 @@ """Implementation Vibrator for Android.""" + from jnius import autoclass from plyer.facades import Vibrator from plyer.platforms.android import activity From c550d14e97c5896fd311ad8a7b43a3599cd55367 Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sun, 3 May 2015 14:33:40 +0200 Subject: [PATCH 13/22] removed unnessesary docstrings --- examples/vibrator/main.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/vibrator/main.py b/examples/vibrator/main.py index c6e14b782..e0e896b84 100644 --- a/examples/vibrator/main.py +++ b/examples/vibrator/main.py @@ -1,4 +1,3 @@ -"""Plyer Vibrator Example.""" from kivy.app import App from kivy.lang import Builder from kivy.uix.boxlayout import BoxLayout @@ -40,10 +39,8 @@ class VibrationInterface(BoxLayout): class VibrationApp(App): - """Main Application.""" def build(self): - """Return root widget.""" return VibrationInterface() def on_pause(self): From 53a8eeee9a605e584dae3fbb9b610ce9069078e6 Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sun, 3 May 2015 14:38:59 +0200 Subject: [PATCH 14/22] added unsupported exception --- plyer/platforms/android/vibrator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plyer/platforms/android/vibrator.py b/plyer/platforms/android/vibrator.py index 78177c997..c1703499e 100644 --- a/plyer/platforms/android/vibrator.py +++ b/plyer/platforms/android/vibrator.py @@ -39,7 +39,7 @@ def _pattern(self, pattern=(0, 1), repeat=-1): def _exists(self): if SDK_INT >= 11: return vibrator.hasVibrator() - return + return NotImplementedError() def _cancel(self): vibrator.cancel() From 1fd5874ff22b5b57b793cae4e1b65052ddc8fc63 Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sun, 3 May 2015 14:53:41 +0200 Subject: [PATCH 15/22] added exist for sdk < 11 --- plyer/platforms/android/vibrator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plyer/platforms/android/vibrator.py b/plyer/platforms/android/vibrator.py index c1703499e..f650f7450 100644 --- a/plyer/platforms/android/vibrator.py +++ b/plyer/platforms/android/vibrator.py @@ -39,7 +39,9 @@ def _pattern(self, pattern=(0, 1), repeat=-1): def _exists(self): if SDK_INT >= 11: return vibrator.hasVibrator() - return NotImplementedError() + elif activity.getSystemService(Context.VIBRATOR_SERVICE) is None: + raise NotImplementedError() + return True def _cancel(self): vibrator.cancel() From 6fd85aca4287a575bdc8d54ac83f6d80ba7e92d2 Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sun, 3 May 2015 15:04:33 +0200 Subject: [PATCH 16/22] replace """ in docstrings" --- examples/vibrator/main.py | 2 +- plyer/platforms/android/vibrator.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/vibrator/main.py b/examples/vibrator/main.py index e0e896b84..d7b0f30d6 100644 --- a/examples/vibrator/main.py +++ b/examples/vibrator/main.py @@ -34,7 +34,7 @@ class VibrationInterface(BoxLayout): - """Root Widget.""" + '''Root Widget.''' pass diff --git a/plyer/platforms/android/vibrator.py b/plyer/platforms/android/vibrator.py index f650f7450..123f69252 100644 --- a/plyer/platforms/android/vibrator.py +++ b/plyer/platforms/android/vibrator.py @@ -1,4 +1,4 @@ -"""Implementation Vibrator for Android.""" +'''Implementation Vibrator for Android.''' from jnius import autoclass from plyer.facades import Vibrator @@ -11,7 +11,7 @@ class AndroidVibrator(Vibrator): - """Android Vibrator class. + '''Android Vibrator class. Supported features: * vibrate for some period of time. @@ -24,7 +24,7 @@ class AndroidVibrator(Vibrator): Android ver. 3.0.x (SDK >= 11) and above. For android with SDK < 11 it just returns `None`. - """ + ''' def _vibrate(self, time=1): if vibrator: @@ -48,8 +48,8 @@ def _cancel(self): def instance(): - """Returns Vibrator with android features. + '''Returns Vibrator with android features. :return: instance of class AndroidVibrator - """ + ''' return AndroidVibrator() From c92fedb56624fda80f7b6fd771ca3be56b8e0760 Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sun, 3 May 2015 15:12:42 +0200 Subject: [PATCH 17/22] merging --- plyer/platforms/android/vibrator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plyer/platforms/android/vibrator.py b/plyer/platforms/android/vibrator.py index 123f69252..837655035 100644 --- a/plyer/platforms/android/vibrator.py +++ b/plyer/platforms/android/vibrator.py @@ -5,7 +5,6 @@ from plyer.platforms.android import activity from plyer.platforms.android import SDK_INT -Intent = autoclass('android.content.Intent') Context = autoclass('android.content.Context') vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE) From 54c458a0348c2a6a7a775e3a81c4097a8087fe69 Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sun, 3 May 2015 15:17:13 +0200 Subject: [PATCH 18/22] merging --- examples/vibrator/main.py | 6 ++--- plyer/facades.py | 2 +- plyer/platforms/android/vibrator.py | 36 +++++++++++++++++++++++++---- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/examples/vibrator/main.py b/examples/vibrator/main.py index 3644b4e93..d7b0f30d6 100644 --- a/examples/vibrator/main.py +++ b/examples/vibrator/main.py @@ -1,8 +1,6 @@ - from kivy.app import App -from kivy.uix.boxlayout import BoxLayout from kivy.lang import Builder - +from kivy.uix.boxlayout import BoxLayout Builder.load_string(''' #:import vibrator plyer.vibrator @@ -36,10 +34,12 @@ class VibrationInterface(BoxLayout): + '''Root Widget.''' pass class VibrationApp(App): + def build(self): return VibrationInterface() diff --git a/plyer/facades.py b/plyer/facades.py index 10ec41038..e8a948531 100644 --- a/plyer/facades.py +++ b/plyer/facades.py @@ -251,7 +251,7 @@ def vibrate(self, time=1): def _vibrate(self, **kwargs): raise NotImplementedError() - def pattern(self, pattern=[0, 1], repeat=-1): + def pattern(self, pattern=(0, 1), repeat=-1): '''Ask the vibrator to vibrate with the given pattern, with an optional repeat. diff --git a/plyer/platforms/android/vibrator.py b/plyer/platforms/android/vibrator.py index 4642957ea..837655035 100644 --- a/plyer/platforms/android/vibrator.py +++ b/plyer/platforms/android/vibrator.py @@ -1,28 +1,54 @@ +'''Implementation Vibrator for Android.''' + from jnius import autoclass from plyer.facades import Vibrator from plyer.platforms.android import activity +from plyer.platforms.android import SDK_INT Context = autoclass('android.content.Context') vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE) class AndroidVibrator(Vibrator): - def _vibrate(self, time=None, **kwargs): + '''Android Vibrator class. + + Supported features: + * vibrate for some period of time. + * vibrate from given pattern. + * cancel vibration. + * check whether Vibrator exists. + + .. warning:: + Feature check if Vibrator exists works only for + Android ver. 3.0.x (SDK >= 11) and above. For android with SDK < 11 + it just returns `None`. + + ''' + + def _vibrate(self, time=1): if vibrator: vibrator.vibrate(int(1000 * time)) - def _pattern(self, pattern=None, repeat=None, **kwargs): + def _pattern(self, pattern=(0, 1), repeat=-1): pattern = [int(1000 * time) for time in pattern] if vibrator: vibrator.vibrate(pattern, repeat) - def _exists(self, **kwargs): - return vibrator.hasVibrator() + def _exists(self): + if SDK_INT >= 11: + return vibrator.hasVibrator() + elif activity.getSystemService(Context.VIBRATOR_SERVICE) is None: + raise NotImplementedError() + return True - def _cancel(self, **kwargs): + def _cancel(self): vibrator.cancel() def instance(): + '''Returns Vibrator with android features. + + :return: instance of class AndroidVibrator + ''' return AndroidVibrator() From 0fde74519d23a89d3314a8edbd131e542e108074 Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sun, 3 May 2015 21:30:01 +0200 Subject: [PATCH 19/22] remove unnessesary docstring --- plyer/platforms/android/vibrator.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/plyer/platforms/android/vibrator.py b/plyer/platforms/android/vibrator.py index 837655035..2625f8761 100644 --- a/plyer/platforms/android/vibrator.py +++ b/plyer/platforms/android/vibrator.py @@ -17,12 +17,6 @@ class AndroidVibrator(Vibrator): * vibrate from given pattern. * cancel vibration. * check whether Vibrator exists. - - .. warning:: - Feature check if Vibrator exists works only for - Android ver. 3.0.x (SDK >= 11) and above. For android with SDK < 11 - it just returns `None`. - ''' def _vibrate(self, time=1): From 10cb2499811e3e80f01ab4306125243f7f8b1ff4 Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sat, 11 Apr 2015 09:59:44 +0200 Subject: [PATCH 20/22] updated document --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a02ec19a7..8498806ba 100644 --- a/README.rst +++ b/README.rst @@ -26,7 +26,7 @@ GPS X X X Notifications X X X X X Text to speech X X X X X X Email (open mail client) X X X X X -Vibrator X +Vibrator X X Sms (send messages) X X Compass X X X Unique ID (IMEI or SN) X X X X X X From 2eafcbf92b5854e8b07c9d4640919053e81bfdb3 Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sun, 3 May 2015 21:35:29 +0200 Subject: [PATCH 21/22] removing files tests --- examples/tests/buildozer.spec | 189 --------------------------- examples/tests/main.py | 176 ------------------------- examples/tests/recycleview.py | 237 ---------------------------------- 3 files changed, 602 deletions(-) delete mode 100644 examples/tests/buildozer.spec delete mode 100644 examples/tests/main.py delete mode 100644 examples/tests/recycleview.py diff --git a/examples/tests/buildozer.spec b/examples/tests/buildozer.spec deleted file mode 100644 index f959e7470..000000000 --- a/examples/tests/buildozer.spec +++ /dev/null @@ -1,189 +0,0 @@ -[app] - -# (str) Title of your application -title = Plyer Tests - -# (str) Package name -package.name = plyer.test - -# (str) Package domain (needed for android/ios packaging) -package.domain = org.test - -# (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 - -# (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 = tests, bin - -# (list) List of exclusions using pattern matching -#source.exclude_patterns = license,images/*/*.jpg - -# (str) Application versioning (method 1) -# version.regex = __version__ = ['"](.*)['"] -# version.filename = %(source.dir)s/main.py - -# (str) Application versioning (method 2) -version = 0.1 - -# (list) Application requirements -# comma seperated e.g. requirements = sqlite3,kivy -requirements = kivy,plyer - -# (list) Garden requirements -#garden_requirements = - -# (str) Presplash of the application -#presplash.filename = %(source.dir)s/data/presplash.png - -# (str) Icon of the application -#icon.filename = %(source.dir)s/data/icon.png - -# (str) Supported orientation (one of landscape, portrait or all) -orientation = portrait - -# (bool) Indicate if the application should be fullscreen or not -fullscreen = 0 - - -# -# Android specific -# - -# (list) Permissions -#android.permissions = INTERNET - -# (int) Android API to use -#android.api = 14 - -# (int) Minimum API required (8 = Android 2.2 devices) -#android.minapi = 8 - -# (int) Android SDK version to use -#android.sdk = 21 - -# (str) Android NDK version to use -#android.ndk = 9c - -# (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 = - -# (str) Android SDK directory (if empty, it will be automatically downloaded.) -#android.sdk_path = - -# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) -#android.p4a_dir = - -# (list) python-for-android whitelist -#android.p4a_whitelist = - -# (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 - -# (list) List of Java files to add to the android project (can be java or a -# directory containing the files) -#android.add_src = - -# (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 tag -#android.manifest.intent_filters = - -# (list) Android additionnal libraries to copy into libs/armeabi -#android.add_libs_armeabi = libs/android/*.so -#android.add_libs_armeabi_v7a = libs/android-v7/*.so -#android.add_libs_x86 = libs/android-x86/*.so -#android.add_libs_mips = libs/android-mips/*.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 - -# (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 = - -# -# 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: ()" - -# (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 - -# (int) Display warning if buildozer is run as root (0 = False, 1 = True) -warn_on_root = 1 - - -# ----------------------------------------------------------------------------- -# 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 diff --git a/examples/tests/main.py b/examples/tests/main.py deleted file mode 100644 index d4eb42a28..000000000 --- a/examples/tests/main.py +++ /dev/null @@ -1,176 +0,0 @@ -from collections import defaultdict -from recycleview import RecycleView -from kivy.base import runTouchApp -from kivy.lang import Builder -from kivy.clock import Clock -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.button import Button -from kivy.properties import ListProperty -from kivy.app import App -from kivy.metrics import sp -import random -from plyer.utils import platform - -import logging - -log = logging.getLogger() - -kv = """ -: - module_name: "" - canvas.before: - Color: - rgb: (.5, .5, .5) - Rectangle: - pos: self.pos - size: self.size - Label: - font_size: "14sp" - color: (0, 1, 0, 1) - text_size: (self.width, None) - text: root.module_name - -: - spacing: "5dp" - test_result: "" - canvas.before: - Color: - rgb: (0, 0, 0) - Rectangle: - pos: self.pos - size: self.size - Label: - font_size: "14sp" - text: root.test_result - color: (0, 1, 0, 1) - text_size: (self.width, None) - -# app example -BoxLayout: - orientation: "vertical" - BoxLayout: - padding: "2sp" - spacing: "2sp" - size_hint_y: None - height: "48sp" - - Button: - text: "Sort data" - on_release: app.sort_data() - - Button: - text: "Generate new data" - on_release: app.generate_new_data() - - RecycleView: - id: rv - -""" - -registered_modules = defaultdict(list) - - -def register_test(function_name): - def decorator(f): - module_name = function_name.split('.')[1] - registered_modules[module_name].append((function_name, f)) - return f - return decorator - - -@register_test('plyer.vibrator.vibrate()') -def test_vibrator_vibrate(): - from plyer import vibrator - vibrator.vibrate() - - -@register_test('plyer.vibrator.vibrate(2s)') -def test_vibrator_vibrate(): - from plyer import vibrator - vibrator.vibrate(2) - - -@register_test('plyer.vibrator.cancel()') -def test_vibrator_vibrate(): - from plyer import vibrator - vibrator.cancel() - - -@register_test('plyer.vibrator.pattern([0.5, 0.1, 0.4])') -def test_vibrator_vibrate(): - from plyer import vibrator - vibrator.pattern([0.5, 0.1, 0.4]) - - -class State: - - Ok = 'Ok' - NotImplemented = 'Not Implemented' - Error = 'Error' - - -class TestViewApp(App): - - results = ListProperty() - rv = RecycleView() - - def build(self): - self.root = Builder.load_string(kv) - self.rv = self.root.ids.rv - self.rv.key_viewclass = "viewclass" - self.rv.key_size = "height" - self.rv.data = self.results - self.run_tests() - - def try_method(self, f): - try: - f() - log.info(State.Ok) - return State.Ok - except NotImplementedError: - log.info(State.NotImplemented) - return State.NotImplemented - except Exception: - log.info(State.Error) - return State.Error - - def run_tests(self): - - for module, functions in registered_modules.iteritems(): - log.info('Testing module %s', module) - try: - __import__('plyer.platforms.%s.%s' % (platform, module)) - except ImportError: - self._add_separator(module, State.NotImplemented) - - total = len(functions) - passed = 0 - for name, function in functions: - log.info('Testing function %s', name) - result = self.try_method(function) - if result == State.Ok: - passed += 1 - self._add_test_result(name, result) - - def _add_separator(self, module_name, state=None): - self.results.append({ - "viewclass": "Separator", - "height": sp(20), - "index": len(self.results), - "module_name": '%25s %s' % (module_name, state) - }) - self.rv.refresh_from_data(force=True) - - def _add_test_result(self, test_name, state): - self.rv.data.append({ - "viewclass": "LogItem", - "index": len(self.results), - "test_result": "{} {}".format( - state, - test_name, - ) - }) - self.rv.refresh_from_data(force=True) - -TestViewApp().run() - diff --git a/examples/tests/recycleview.py b/examples/tests/recycleview.py deleted file mode 100644 index 3f80eb03b..000000000 --- a/examples/tests/recycleview.py +++ /dev/null @@ -1,237 +0,0 @@ -""" -RecycleView -=========== - -Data accepted: list of dict. - -TODO: - - recycle old widgets based on the class - - add custom function to get view height - - add custom function to get view class - - update view size when created - - move all internals to adapter - - selection -""" - -from kivy.compat import string_types -from kivy.uix.relativelayout import RelativeLayout -from kivy.lang import Builder -from kivy.properties import NumericProperty, AliasProperty, StringProperty, \ - ObjectProperty -from kivy.factory import Factory -from kivy.clock import Clock - -Builder.load_string(""" -: - ScrollView: - id: sv - do_scroll_x: False - on_scroll_y: root.refresh_from_data() - RecycleViewLayout: - id: layout - size_hint: None, None - size: root.width, root.computed_height -""") - - -class RecycleViewLayout(RelativeLayout): - pass - - -class RecycleView(RelativeLayout): - - data = ObjectProperty() - adapter = ObjectProperty() - - default_height = NumericProperty("48dp") - key_height = StringProperty() - viewclass = ObjectProperty() - key_viewclass = StringProperty() - - # internals - computed_height = NumericProperty(0) - computed_heights = [] - computed_positions = [] - views = {} - dirty_views = {} - - def on_viewclass(self, instance, value): - if isinstance(value, string_types): - self.viewclass = getattr(Factory, value) - - def do_layout(self, *args): - super(RecycleView, self).do_layout(*args) - self.refresh_from_data(True) - - def make_view_dirty(self, view, index): - viewclass = view.__class__ - if viewclass not in self.dirty_views: - self.dirty_views[viewclass] = {index: view} - else: - self.dirty_views[viewclass][index] = view - - def refresh_from_data(self, force=False): - """The data has changed, update the RecycleView internals - """ - if force: - for index, view in self.views.items(): - self.make_view_dirty(view, index) - self.views = {} - self.compute_views_heights() - self.compute_visible_views() - - def compute_views_heights(self): - """(internal) Calculate all the views height according to - default_height, key_height, and then calculate their future positions - """ - height = 0 - key_height = self.key_height - default_height = self.default_height - self.computed_heights = [ - item.get(key_height, default_height) - for item in self.data - ] - self.computed_height = sum(self.computed_heights) - self.computed_positions = list( - self._compute_positions(self.computed_heights)) - - def _compute_positions(self, heights): - y = 0 - for height in heights: - yield y - y += height - - def compute_visible_views(self): - """(internal) Determine the views that need to be showed in the current - scrollview. All the hidden views will be flagged as dirty, and might - be resued for others views. - """ - # determine the view to create for the scrollview y / height - sv = self.ids.sv - layout = self.ids.layout - scroll_y = 1 - (min(1, max(sv.scroll_y, 0))) - px_start = (layout.height - self.height) * scroll_y - px_end = px_start + self.height - - # now calculate the view indices we must show - i_start = self.get_view_index_at(px_start) - i_end = self.get_view_index_at(px_end) - - current_views = self.views - visible_views = {} - dirty_views = self.dirty_views - - # iterate though the visible view - # add them into the layout if not already done - for index in range(i_start, i_end + 1): - view = self.get_view(index) - if not view: - continue - - visible_views[index] = view - current_views.pop(index, None) - - # add to the layout if it's not already done - if view.parent: - continue - layout.add_widget(view) - - # put all the hidden view as dirty views - for index, view in current_views.items(): - layout.remove_widget(view) - self.make_view_dirty(view, index) - - # save the current visible views - self.views = visible_views - - def get_view(self, index): - """Return a view instance for the `index` - """ - if index in self.views: - return self.views[index] - - dirty_views = self.dirty_views - viewclass = self.get_viewclass(index) - if viewclass in dirty_views: - - # we found ourself in the dirty list, no need to update data! - if index in dirty_views[viewclass]: - view = dirty_views[viewclass].pop(index) - self.refresh_view_layout(view, index) - self.views[index] = view - return view - - # we are not in the dirty list, just take one and reuse it. - if dirty_views[viewclass]: - previous_index = dirty_views[viewclass].keys()[-1] - view = dirty_views[viewclass].pop(previous_index) - # update view data - item = self.data[index] - for key, value in item.items(): - setattr(view, key, value) - self.refresh_view_layout(view, index) - self.views[index] = view - return view - - # create a fresh one - self.views[index] = view = self.create_view(index) - self.refresh_view_layout(view, index) - return view - - def refresh_view_layout(self, view, index): - """(internal) Refresh the layout of a view. Size and pos are determine - by the `RecycleView` according to the view `index` informations - """ - view.size_hint = None, None - view.width = self.width - view.height = h = self.computed_heights[index] - view.y = self.computed_height - self.computed_positions[index] - h - - def create_view(self, index): - """Create the view for the `index` - """ - viewclass = self.get_viewclass(index) - item = self.data[index] - # FIXME: we could pass the data though the constructor, but that wont - # work for kv-declared classes, and might lead the user to think it can - # work for reloading as well. - view = viewclass(**item) - for key, value in item.items(): - setattr(view, key, value) - return view - - def get_view_position(self, index): - """Get the position for the view at `index` - """ - return self.computed_positions[index] - - def get_view_height(self, index): - """Get the height for the view at `index` - """ - return self.computed_heights[index] - - def get_viewclass(self, index): - """Get the class needed to create the view `index` - """ - viewclass = None - if self.key_viewclass: - viewclass = self.data[index].get(self.key_viewclass) - viewclass = getattr(Factory, viewclass) - if not viewclass: - viewclass = self.viewclass - return viewclass - - def get_view_index_at(self, y): - """Return the view `index` for the `y` position - """ - for index, pos in enumerate(self.computed_positions): - if pos > y: - return index - 1 - return index - - def on_data(self, instance, value): - # data changed, right now, remove all the widgets. - self.dirty_views = {} - self.views = {} - self.ids.layout.clear_widgets() - self._trigger_layout() From ab454dc65f95a5a48846a497dcafca120831530d Mon Sep 17 00:00:00 2001 From: Andrzej Grymkowski Date: Sun, 3 May 2015 21:59:32 +0200 Subject: [PATCH 22/22] added defaults --- plyer/platforms/android/vibrator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plyer/platforms/android/vibrator.py b/plyer/platforms/android/vibrator.py index 2625f8761..c28fe8e21 100644 --- a/plyer/platforms/android/vibrator.py +++ b/plyer/platforms/android/vibrator.py @@ -19,24 +19,24 @@ class AndroidVibrator(Vibrator): * check whether Vibrator exists. ''' - def _vibrate(self, time=1): + def _vibrate(self, time=None, **kwargs): if vibrator: vibrator.vibrate(int(1000 * time)) - def _pattern(self, pattern=(0, 1), repeat=-1): + def _pattern(self, pattern=None, repeat=None, **kwargs): pattern = [int(1000 * time) for time in pattern] if vibrator: vibrator.vibrate(pattern, repeat) - def _exists(self): + def _exists(self, **kwargs): if SDK_INT >= 11: return vibrator.hasVibrator() elif activity.getSystemService(Context.VIBRATOR_SERVICE) is None: raise NotImplementedError() return True - def _cancel(self): + def _cancel(self, **kwargs): vibrator.cancel()