diff --git a/README.android b/README.android index 3bce006819..66411e0398 100644 --- a/README.android +++ b/README.android @@ -63,9 +63,6 @@ Cardsets: LB230301. - (After new changes as of 27.3.23 - not contained in the last f-droid - version): - Allow external storage access for Android 10 and later. - on Android 10 it ist still possible to write to the extenal storage. A @@ -83,3 +80,10 @@ LB230301. never query you for that. You may do it on your own. LB230327. + + Scheduled for the next Android release: + - Screen rotation lock. + - Toolbar actualised + - Protection from accidental reset or redeal. + +LB230919. diff --git a/README.md b/README.md index 3085b33989..0fa653fe50 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ mkdir -p "$PKGTREE" ### Alternate toolkit. -- Kivy (10.0 or later) +- Kivy - Features: - Sound support integrated. - Android apk build support. @@ -189,7 +189,11 @@ On the basis of Kivy an Android App is also available. You may build your own using appropriate build instructions in README.android and in Directory buildozer. -Some versions will also be published on F-droid (https://f-droid.org) +Some versions will also be published on F-droid. + +[Get it on F-Droid](https://f-droid.org/packages/org.lufebe16.pysolfc/) ### Configuring Freecell Solver diff --git a/android/NOTE b/android/NOTE index 1259348d87..b43a587226 100644 --- a/android/NOTE +++ b/android/NOTE @@ -5,3 +5,7 @@ Please use the new setup presented in directory buildozer. LB221118. + +This Directory will soon be deleted from the Repo. + +LB230919. diff --git a/buildozer/pubspec.json b/buildozer/pubspec.json index 9a47aa2ca7..c660e60c66 100644 --- a/buildozer/pubspec.json +++ b/buildozer/pubspec.json @@ -11,8 +11,8 @@ "type": "SINGLE", "filters": [], "attributes": [], - "versionCode": 102122001, - "versionName": "2.20.1", + "versionCode": 102122100, + "versionName": "2.21.0", "outputFile": "pysolfc-release.apk" } ], diff --git a/fastlane/metadata/android/en-US/changelogs/102122100.txt b/fastlane/metadata/android/en-US/changelogs/102122100.txt new file mode 100644 index 0000000000..14e0d4194f --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/102122100.txt @@ -0,0 +1,10 @@ +Android specific: +- Temporary screen orientation lock added. Long tap (5 seconds) to the + playground locks/unlocks (toggle) automatic screen rotation. +- Toolbar: configuration of displayed toolbar buttons added to options menu. +- Toolbar: dynamic updates on Toolbar and Options settings. +- Toolbar buttons for 'new deal' and 'restart' have now a delayed + functionality. This prevents from accidental execution. +Main version: +- consult NEWS.asscidoc or html-src/news.html on + github for more change informations. diff --git a/pysollib/app.py b/pysollib/app.py index 5f1b79c6cd..5294182937 100644 --- a/pysollib/app.py +++ b/pysollib/app.py @@ -430,16 +430,6 @@ def runGame(self, id_, random=None): self.toolbar.config( 'shuffle', self.opt.toolbar_vars['shuffle'] and self.game.canShuffle()) - if TOOLKIT == 'kivy': - self.toolbar.config( - 'undo', - self.opt.toolbar_vars['undo'] and self.game.canUndo()) - self.toolbar.config( - 'undo', - self.opt.toolbar_vars['redo'] and self.game.canRedo()) - self.toolbar.config( - 'autodrop', - self.opt.toolbar_vars['autodrop'] and not self.game.canShuffle()) # noqa # delete intro progress bar if self.intro.progress: self.intro.progress.destroy() diff --git a/pysollib/kivy/LApp.py b/pysollib/kivy/LApp.py index a2a94e99f5..b6651ddb99 100644 --- a/pysollib/kivy/LApp.py +++ b/pysollib/kivy/LApp.py @@ -38,6 +38,7 @@ from kivy.graphics import Line from kivy.graphics import Rectangle from kivy.graphics import Triangle +from kivy.properties import NumericProperty from kivy.properties import StringProperty from kivy.uix.actionbar import ActionButton from kivy.uix.actionbar import ActionPrevious @@ -52,7 +53,6 @@ from kivy.uix.treeview import TreeViewLabel from kivy.uix.widget import Widget from kivy.utils import platform -from kivy.properties import NumericProperty from pysollib.kivy.androidperms import requestStoragePerm from pysollib.kivy.androidrot import AndroidScreenRotation diff --git a/pysollib/kivy/menubar.py b/pysollib/kivy/menubar.py index b7cab6bc5b..1c90638433 100644 --- a/pysollib/kivy/menubar.py +++ b/pysollib/kivy/menubar.py @@ -1112,10 +1112,11 @@ def buildTree(self, tv, node): # 'Top', # self.menubar.tkopt.toolbar, 1, # self.menubar.mOptToolbar) + # self.addRadioNode(tv, rg, - # 'Bottom', - # self.menubar.tkopt.toolbar, 2, - # self.menubar.mOptToolbar) + # _('Bottom'), + # self.menubar.tkopt.toolbar, 2, + # self.menubar.mOptToolbar) self.addRadioNode(tv, rg, _('Left'), @@ -1126,6 +1127,17 @@ def buildTree(self, tv, node): self.menubar.tkopt.toolbar, 4, self.menubar.mOptToolbar) + rg1 = tv.add_node( + LTreeNode(text=_('Buttons:')), rg) + if rg1: + for w in TOOLBAR_BUTTONS: + ww = w + ww[0].upper() + self.addCheckNode(tv, rg, + _(ww), # noqa + self.menubar.tkopt.toolbar_vars[w], + self.make_vars_command(self.menubar.mOptToolbarConfig, w)) # noqa + # ------------------------------------------- # Statusbar - not implemented @@ -2441,7 +2453,6 @@ def toolbarConfig(self, w, v): if self._cancelDrag(break_pause=False): return self.app.opt.toolbar_vars[w] = v - self.app.toolbar.config(w, v) self.top.update_idletasks() # diff --git a/pysollib/kivy/tkconst.py b/pysollib/kivy/tkconst.py index ea24a9c617..75048dfa78 100644 --- a/pysollib/kivy/tkconst.py +++ b/pysollib/kivy/tkconst.py @@ -53,18 +53,19 @@ TOOLBAR_BUTTONS = ( "new", "restart", - "open", - "save", + # "open", + # "save", "undo", "redo", "autodrop", "shuffle", + "hint", "pause", - "statistics", + # "statistics", "rules", - "quit", - "player", -) + # "quit", + # "player", + ) STATUSBAR_ITEMS = ( ('stuck', "'You Are Stuck' indicator"), diff --git a/pysollib/kivy/toast.py b/pysollib/kivy/toast.py index 685002fc9d..76ac037267 100644 --- a/pysollib/kivy/toast.py +++ b/pysollib/kivy/toast.py @@ -17,12 +17,11 @@ def __init__(self, **kw): self.duration = 4.0 self.tsize = self.size - self.rsize = 20 + self.rsize = [20,] with self.canvas.before: Color(0.2, 0.2, 0.2, 0.8) self.rect = RoundedRectangle() self.bind(size=self._update_rect) - self.bind(texture_size=self.eval_size) def eval_size(self,instance,size): width, height = size @@ -33,14 +32,17 @@ def eval_size(self,instance,size): ads = height * 1.7 self.tsize = (width + ads, height + ads) self.rsize = [(ads+height)/2.0,] - #print(self.tsize,self.rsize) + # print(self.tsize, self.rsize) def _update_rect(self, instance, value): self.rect.size = self.tsize - self.rect.pos = (instance.center_x-self.tsize[0]/2.0,instance.center_y-self.tsize[1]/2.0) + # print(self.top) + # print(self.pos_hint) + self.rect.pos = (self.center_x-self.tsize[0]/2.0,self.center_y-self.tsize[1]/2.0) self.rect.radius = self.rsize def stop(self, *args): + self.unbind(texture_size=self.eval_size) self.parent.remove_widget(self) def hide(self, *args): @@ -54,6 +56,7 @@ def show(self, parent=None, duration=2.0): return self.duration = duration parent.add_widget(self) + self.bind(texture_size=self.eval_size) anim = Animation(opacity=1, duration=0.4) anim.start(self) Clock.schedule_once(self.hide,self.duration) @@ -64,5 +67,6 @@ def start(self,parent=None): return self.opacity = 1 parent.add_widget(self) + self.bind(texture_size=self.eval_size) # ================================================================ diff --git a/pysollib/kivy/toolbar.py b/pysollib/kivy/toolbar.py index d170a4710c..3e19cd3bed 100644 --- a/pysollib/kivy/toolbar.py +++ b/pysollib/kivy/toolbar.py @@ -22,33 +22,33 @@ import os from time import time +from kivy.cache import Cache +from kivy.clock import Clock +from kivy.properties import BooleanProperty +from kivy.uix.behaviors import ButtonBehavior +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.image import Image as KivyImage + +# PySol kivy imports +from pysollib.kivy.LApp import LBase +from pysollib.kivy.LApp import LImage +from pysollib.kivy.toast import Toast + # PySol imports -from pysollib.mygettext import _, n_ +from pysollib.mygettext import _, n_ # noqa from pysollib.util import IMAGE_EXTENSIONS from pysollib.winsystems import TkSettings -# ************************************************************************ -# * -# ************************************************************************ - -from pysollib.kivy.LApp import LImage -from pysollib.kivy.LApp import LBase -from pysollib.kivy.toast import Toast -# from LApp import LMainWindow -from kivy.uix.boxlayout import BoxLayout -# from kivy.uix.button import Button -from kivy.uix.behaviors import ButtonBehavior -# from kivy.uix.behaviors import ToggleButtonBehavior -from kivy.uix.image import Image as KivyImage # ************************************************************************ -from kivy.cache import Cache - +class MyButtonBase(ButtonBehavior, KivyImage, LBase): + shown = BooleanProperty(True) + enabled = BooleanProperty(True) + config = BooleanProperty(True) -class MyButton(ButtonBehavior, KivyImage, LBase): def __init__(self, **kwargs): - super(MyButton, self).__init__(**kwargs) + super(MyButtonBase, self).__init__(**kwargs) self.src = None if ('image' in kwargs): self.src = kwargs['image'].source @@ -60,7 +60,23 @@ def __init__(self, **kwargs): self.name = kwargs['name'] self.source = self.src self.allow_stretch = True - self.shown = True + + def set_shown(self, instance, value): + # print ('** set shown (',self.name ,') called', value) + self.shown = value + + def set_enabled(self, instance, value): + # print ('** set enabled (',self.name ,') called', value) + self.enabled = value + + def set_config(self, instance, value): + # print ('** set config (',self.name ,') called', value) + self.config = value + + +class MyButton(MyButtonBase): + def __init__(self, **kwargs): + super(MyButton, self).__init__(**kwargs) def on_press(self): self.allow_stretch = False @@ -71,28 +87,16 @@ def on_release(self): self.command() -class MyCheckButton(ButtonBehavior, KivyImage, LBase): +class MyCheckButton(MyButtonBase): def __init__(self, **kwargs): super(MyCheckButton, self).__init__(**kwargs) - self.src = None - if ('image' in kwargs): - self.src = kwargs['image'].source - self.command = None - if ('command' in kwargs): - self.command = kwargs['command'] - self.name = "" - if ('name' in kwargs): - self.name = kwargs['name'] self.variable = None if ('variable' in kwargs): self.variable = kwargs['variable'] self.win = None if ('win' in kwargs): self.win = kwargs['win'] - self.source = self.src - self.allow_stretch = True self.checked = False - self.shown = True # self.variable = self.win.app.menubar.tkopt.pause if self.variable: @@ -139,24 +143,12 @@ def on_release(self): pass -class MyToastButton(ButtonBehavior, KivyImage, LBase): +class MyToastButton(MyButtonBase): def __init__(self, **kwargs): super(MyToastButton, self).__init__(**kwargs) - self.src = None - if ('image' in kwargs): - self.src = kwargs['image'].source - self.command = None - if ('command' in kwargs): - self.command = kwargs['command'] - self.name = "" - if ('name' in kwargs): - self.name = kwargs['name'] self.timeout = 0.0 if ('timeout' in kwargs): self.timeout = kwargs['timeout'] - self.source = self.src - self.allow_stretch = True - self.shown = True self.start_time = 0.0 def on_press(self): @@ -172,10 +164,46 @@ def on_release(self): else: mainApp = Cache.get('LAppCache', 'mainApp') toast = Toast(text=_("button released too early")) + # toast = Toast(text=_("button released too early"),pos_hint={'top': 0.8}) # noqa + # pos hint wirkt nur auf den text, nicht auf die box !!! toast.show(parent=mainApp.baseWindow, duration=2.0) # print('too early released') +class MyWaitButton(MyButtonBase): + def __init__(self, **kwargs): + super(MyWaitButton, self).__init__(**kwargs) + self.timeout = 0.0 + if ('timeout' in kwargs): + self.timeout = kwargs['timeout'] + self.start_time = 0.0 + self.eventId = None + self.wait_toast = None + + def time_out(self, *args): + self.wait_toast.stop() + self.wait_toast = None + self.eventId = None + if (self.command is not None): + self.command() + # print ('timeout') + + def on_press(self): + self.allow_stretch = False + self.eventId = Clock.schedule_once(self.time_out, 1.0) + mainApp = Cache.get('LAppCache', 'mainApp') + self.wait_toast = Toast(text=_("hold on ...")) + self.wait_toast.start(mainApp.baseWindow) + + def on_release(self): + self.allow_stretch = True + if self.eventId is not None: + Clock.unschedule(self.eventId) + # print ('unscheduled') + if self.wait_toast is not None: + self.wait_toast.stop() + self.wait_toast = None + # ************************************************************************ # * Note: Applications should call show/hide after constructor. # ************************************************************************ @@ -200,6 +228,7 @@ def __init__( self.dir = dir self.win.setTool(self, 3) self.buttons = [] + self.buttond = {} # This is called only once after program start. Configurations # have to take place elsewhere. @@ -237,8 +266,11 @@ def __init__( ]: ''' + # Build all the buttions. + for label, f, t in bl: if label is None: + button = None # We dont have separators in kivy version. pass elif label == 'Pause': @@ -251,6 +283,23 @@ def __init__( button = self._createButton(label, f, check=False, tooltip=t) self.buttons.append(button) + if button is not None: + # print('button name: ', button.name) + self.buttond[button.name] = button + + # check buttons if configurated or opted out. + # (could ev. be integrated into _createButton) + + toolbar_opt = getattr(self.menubar.tkopt, 'toolbar_vars') + for k in toolbar_opt.keys(): + opt = toolbar_opt[k] + if k in self.buttond.keys(): + b = self.buttond[k] + b.config = opt.get() + opt.bind(value=b.set_config) + + self.redraw() + def show(self, on, **kw): side = self.menubar.tkopt.toolbar.get() self.win.setTool(None, side) @@ -268,24 +317,22 @@ def getSize(self): def updateText(self, **kw): pass + def redraw(self): + self.clear_widgets() + for b in self.buttons: + # print(b.name,b.config,b.shown,b.enabled) + if b.shown and b.enabled and b.config: + self.add_widget(b) + + def changed_state(self, instance, value): + self.redraw() + def config(self, w, v): print('********************* PysolToolbarTk: config %s, %s' % (w, v)) - # This is the position, where the toolbar can be configured. - - chgd = False - for b in self.buttons: - if b.name == w: - ov = b.shown - if v != ov: - b.shown = v - chgd = True - - if chgd: - self.clear_widgets() - for b in self.buttons: - if b.shown: - self.add_widget(b) + if w == 'shuffle': + self.buttond['shuffle'].shown = v + self.buttond['autodrop'].shown = not v # Lokale. @@ -334,10 +381,36 @@ def _createButton(self, label, command, check=False, tooltip=None, timeout=0.0): button = MyCheckButton(**kw) elif timeout > 0.0: - button = MyToastButton(**kw) + # button = MyToastButton(**kw) + button = MyWaitButton(**kw) else: button = MyButton(**kw) + try: + oname = name + # redo is handled same way as undo, there is no separate option. + if name == 'redo': + oname = 'undo' + opt = getattr(self.menubar.tkopt, oname) + + # specialisation (differently used options): + # - autodrop button has no own option. option 'autodrop' is used + # for the different effect of fully automatic dropping! + # - pause button sets and clears the pause option, not vice versa! + # it is an option that only exists internaly (not saved). (same + # applies also to the tk and tile implementations) + if oname not in ['autodrop', 'pause']: + button.enabled = opt.get() + # print('** ', oname, '(enabled) = ', opt.get()) + opt.bind(value=button.set_enabled) + + button.bind(enabled=self.changed_state) + button.bind(shown=self.changed_state) + except: # noqa + pass + + button.bind(config=self.changed_state) + # TBD: tooltip ev. auf basis einer statuszeile implementieren # if tooltip: # b = MfxTooltip(button)