Simple Timer in Kivy
Fleetingimport os
import math
import time
from kivy.app import App
from kivy.clock import Clock
from kivy.graphics import Color, Rectangle
from kivy.properties import BooleanProperty, NumericProperty, StringProperty
from kivy.uix.boxlayout import BoxLayout
from plyer import orientation
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.progressbar import ProgressBar
from kivy.uix.screenmanager import Screen, ScreenManager, ScreenManagerException
from kivy.core.audio import SoundLoader
from helpers.osc import oschandler, to_service
from helpers.wakelock import WakeLock
from android.runnable import run_on_ui_thread
from logging import getLogger
from kivy.lang import Builder
logger = getLogger(__name__)
pink = (1, 0.75, 0.8)
orange = (1, 0.5, 0)
cyan = (0, 1, 1)
Builder.load_string("""
# copy from the source code of kivy, but increased
<ProgressBar>:
canvas:
Color:
rgb: 1, 1, 1
BorderImage:
border: (12, 12, 12, 12)
pos: self.x, self.y
size: self.width, self.height
source: 'atlas://data/images/defaulttheme/progressbar_background'
BorderImage:
border: [int(min(self.width * (self.value / float(self.max)) if self.max else 0, 12))] * 4
pos: self.x, self.y
size: self.width * (self.value / float(self.max)) if self.max else 0, self.height
source: 'atlas://data/images/defaulttheme/progressbar'
""")
class StateMachine(object):
def __init__(self, manager, duration):
self.manager = manager
self.duration = duration
self.state = "step"
self.timer = self.manager.get_screen('simpletimer:timer')
def update(self):
if self.state == "step":
self.timer.finish()
else:
raise NotImplementedError()
def start(self):
self.timer.start_timer(
what="Tic, Tac...",
color=cyan,
duration=self.duration,
on_done=self.update,
sound_map={}
)
class SettingsScreen(Screen):
duration = NumericProperty(60)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
def __init__(self, **kwargs):
super(SettingsScreen, self).__init__(**kwargs)
with self.canvas.before:
self.color = Color(1, 1, 1, 1)
self.rect = Rectangle(size=self.size, pos=self.pos)
self.bind(size=self._update_rect, pos=self._update_rect)
layout = BoxLayout(orientation='vertical')
duration_layout = BoxLayout(size_hint=(1, 0.15))
duration_layout.add_widget(
Button(text="-",
background_color=(0, 0, 1, 1),
color=(1, 1, 1, 1),
on_press=self.decrease_duration))
self.duration_label = Label(text="", color=(0, 0, 0, 1))
duration_layout.add_widget(self.duration_label)
duration_layout.add_widget(
Button(text="+",
background_color=(0, 0, 1, 1),
color=(1, 1, 1, 1),
on_press=self.increase_duration))
layout.add_widget(duration_layout)
# Go button
go_button = Button(text="Go !!!",
background_color=(0, 0, 1, 1),
color=(1, 1, 1, 1),
size_hint=(1, 0.15))
go_button.bind(on_press=self.start_timer)
layout.add_widget(go_button)
self.add_widget(layout)
self.update_display()
self.bind(on_pre_enter=self.pre_enter_handler)
@oschandler("alarmtimer:start")
def _(config):
self.duration = config["duration"]
self.update_display()
self.start_timer()
def pre_enter_handler(self, *_):
to_service("simpletimer:settings")
orientation.set_sensor()
def update_display(self):
self.duration_label.text = "Temps: " + str(self.duration // 60) + "m"
def increase_duration(self, instance):
self.duration += 60
self.update_display()
def decrease_duration(self, instance):
if self.duration > 60:
self.duration -= 60
self.update_display()
def start_timer(self, *instance):
app = App.get_running_app()
app.root.state_machine = StateMachine(
self.manager,
self.duration,
)
app.root.state_machine.start()
class TimerScreen(Screen):
remaining_time = NumericProperty(0)
duration = NumericProperty(0)
timer_running = BooleanProperty(False)
what = StringProperty("")
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
def __init__(self, **kwargs):
super(TimerScreen, self).__init__(**kwargs)
self.leave_is_stop = True
self.alarm = SoundLoader.load("/sdcard/Alarms/alarm.ogg")
if self.alarm is None:
raise Exception("Alarm could not be loaded")
with self.canvas.before:
self.color = Color(0, 1, 1, 1)
self.rect = Rectangle(size=self.size, pos=self.pos)
self.bind(size=self._update_rect, pos=self._update_rect)
layout = GridLayout(cols=1)
self.progress = ProgressBar(size_hint=(1, 0.5))
layout.add_widget(self.progress)
self.time_label = Label(text="", font_size='50sp', color=(0, 0, 0, 1))
layout.add_widget(self.time_label)
self.what_label = Label(text="", font_size='20sp', color=(0, 0, 0, 1))
layout.add_widget(self.what_label)
button_layout = BoxLayout()
self.pause_button = Button(text="Pause",
background_color=(0, 0, 1, 1),
color=(1, 1, 1, 1))
self.pause_button.bind(on_press=self.toggle_clock)
button_layout.add_widget(self.pause_button)
layout.add_widget(button_layout)
self.add_widget(layout)
self.bind(on_pre_enter=self.pre_enter_handler)
self.bind(on_pre_leave=self.pre_leave_handler)
with WakeLock("simpletimer", wakeup=True):
self.keep_screen_on()
def keep_screen_on(self, revert=False):
from android import mActivity
from jnius import autoclass
from android.runnable import run_on_ui_thread
LayoutParams = autoclass('android.view.WindowManager$LayoutParams')
@run_on_ui_thread
def do():
if revert:
logger.debug("Leaving screen always on")
mActivity.getWindow().clearFlags(LayoutParams.FLAG_KEEP_SCREEN_ON)
else:
logger.debug("Entering screen always on")
mActivity.getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON)
do()
time.sleep(0.5)
def pre_leave_handler(self, *args):
self.stop_clock()
if self.alarmtime_clock:
self.alarmtime_clock.cancel()
self.alarmtime_clock = None
self.keep_screen_on(revert=True)
if self.alarm:
self.alarm.stop()
self.alarm = None
from jnius import autoclass
from android import mActivity
View = autoclass('android.view.View')
option = View.SYSTEM_UI_FLAG_VISIBLE
@run_on_ui_thread
def fs():
logger.debug("Leaving fullscreen mode")
mActivity.getWindow().getDecorView().setSystemUiVisibility(option)
fs()
def pre_enter_handler(self, *args):
self.keep_screen_on()
from jnius import autoclass
from android import mActivity
View = autoclass('android.view.View')
option = View.SYSTEM_UI_FLAG_FULLSCREEN
@run_on_ui_thread
def fs():
logger.debug("Entering fullscreen mode")
mActivity.getWindow().getDecorView().setSystemUiVisibility(option)
fs()
def start_timer(
self,
what,
color,
duration,
on_done,
sound_map,
):
self.wanted_color = color
self.color.rgb = self.wanted_color
App.get_running_app().goto("simpletimer:timer")
self.duration = duration
self.what = what
self.elapsed_alarm_time = 0.
self.duration_alarm_fade_in = 90.
self.alarmtime_clock = None
self.remaining_time = duration
self.on_done = on_done
self.finished = False
self.sound_map = sound_map
self.sound = self.sound_map.get(None)
if self.sound:
self.sound.play()
self.start_clock()
def update_display(self):
self.what_label.text = self.what
if self.clock:
self.color.rgb = self.wanted_color
self.pause_button.text = "Pause"
else:
if self.finished:
self.what_label.text = "C'est fini !!!"
self.pause_button.text = "Ok"
else:
self.color.rgb = orange
self.pause_button.text = "Continue ?"
self.time_label.text = str(self.remaining_time)
self.progress.max = self.duration
self.progress.value = self.remaining_time
def countdown(self, dt):
if self.remaining_time > 0:
self.remaining_time -= 1
else:
self.stop_clock()
self.alarmtime_clock = Clock.schedule_interval(self.alarmtime, 1)
self.alarm.loop = True
self.alarm.volume = 0
self.color.rgb = (0, 0, 0)
self.alarm.play()
self.finished = True
self.update_display()
new_sound = self.sound_map.get(self.remaining_time)
if new_sound:
if self.sound:
self.sound.stop()
self.sound = new_sound
self.sound.play()
def alarmtime(self, *_):
self.elapsed_alarm_time += 1
self.time_label.text = str(int(self.elapsed_alarm_time))
if self.elapsed_alarm_time < self.duration_alarm_fade_in:
normalized_time = self.elapsed_alarm_time / self.duration_alarm_fade_in
vol = math.sin(normalized_time * math.pi / 2)
self.color.rgb = (vol,) * 3
logger.debug("Alarm volume: {}".format(vol))
self.alarm.volume = vol
else:
self.alarm.volume = 1.0
def start_clock(self):
self.clock = Clock.schedule_interval(self.countdown, 1)
self.update_display()
def stop_clock(self):
Clock.unschedule(self.clock)
self.clock = None
self.update_display()
def toggle_clock(self, instance=None):
if self.finished:
self.leave()
elif self.clock:
self.stop_clock()
else:
self.start_clock()
def finish(self):
self.finished = True
def leave(self, instance=None):
if self.leave_is_stop:
to_service("exit")
import time
time.sleep(2)
App.get_running_app().stop()
else:
App.get_running_app().back()
class SimpleTimerApp(App):
def goto(self, screen):
self.manager.current = screen
def back(self):
self.manager.current = 'simpletimer:settings'
@staticmethod
def populate(sm):
try:
sm.get_screen('simpletimer:settings')
return
except ScreenManagerException:
pass # not populated yet
sm.add_widget(SettingsScreen(name='simpletimer:settings'))
sm.add_widget(TimerScreen(name='simpletimer:timer'))
def build(self):
sm = ScreenManager()
self.populate(sm)
return sm
def run():
SimpleTimerApp().run()
import os
import math
import time
from kivy.app import App
from kivy.clock import Clock
from kivy.graphics import Color, Rectangle
from kivy.properties import BooleanProperty, NumericProperty, StringProperty
from kivy.uix.boxlayout import BoxLayout
from plyer import orientation
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.progressbar import ProgressBar
from kivy.uix.screenmanager import Screen, ScreenManager, ScreenManagerException
from kivy.core.audio import SoundLoader
from helpers.osc import oschandler, to_service
from helpers.wakelock import WakeLock
from android.runnable import run_on_ui_thread
from logging import getLogger
from kivy.lang import Builder
logger = getLogger(__name__)
pink = (1, 0.75, 0.8)
orange = (1, 0.5, 0)
cyan = (0, 1, 1)
Builder.load_string("""
# copy from the source code of kivy, but increased
<ProgressBar>:
canvas:
Color:
rgb: 1, 1, 1
BorderImage:
border: (12, 12, 12, 12)
pos: self.x, self.y
size: self.width, self.height
source: 'atlas://data/images/defaulttheme/progressbar_background'
BorderImage:
border: [int(min(self.width * (self.value / float(self.max)) if self.max else 0, 12))] * 4
pos: self.x, self.y
size: self.width * (self.value / float(self.max)) if self.max else 0, self.height
source: 'atlas://data/images/defaulttheme/progressbar'
""")
class StateMachine(object):
def __init__(self, manager, duration):
self.manager = manager
self.duration = duration
self.state = "step"
self.timer = self.manager.get_screen('simpletimer:timer')
def update(self):
if self.state == "step":
self.timer.finish()
else:
raise NotImplementedError()
def start(self):
self.timer.start_timer(
what="Tic, Tac...",
color=cyan,
duration=self.duration,
on_done=self.update,
sound_map={}
)
class SettingsScreen(Screen):
duration = NumericProperty(60)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
def __init__(self, **kwargs):
super(SettingsScreen, self).__init__(**kwargs)
with self.canvas.before:
self.color = Color(1, 1, 1, 1)
self.rect = Rectangle(size=self.size, pos=self.pos)
self.bind(size=self._update_rect, pos=self._update_rect)
layout = BoxLayout(orientation='vertical')
duration_layout = BoxLayout(size_hint=(1, 0.15))
duration_layout.add_widget(
Button(text="-",
background_color=(0, 0, 1, 1),
color=(1, 1, 1, 1),
on_press=self.decrease_duration))
self.duration_label = Label(text="", color=(0, 0, 0, 1))
duration_layout.add_widget(self.duration_label)
duration_layout.add_widget(
Button(text="+",
background_color=(0, 0, 1, 1),
color=(1, 1, 1, 1),
on_press=self.increase_duration))
layout.add_widget(duration_layout)
# Go button
go_button = Button(text="Go !!!",
background_color=(0, 0, 1, 1),
color=(1, 1, 1, 1),
size_hint=(1, 0.15))
go_button.bind(on_press=self.start_timer)
layout.add_widget(go_button)
self.add_widget(layout)
self.update_display()
self.bind(on_pre_enter=self.pre_enter_handler)
@oschandler("alarmtimer:start")
def _(config):
self.duration = config["duration"]
self.update_display()
self.start_timer()
def pre_enter_handler(self, *_):
to_service("simpletimer:settings")
orientation.set_sensor()
def update_display(self):
self.duration_label.text = "Temps: " + str(self.duration // 60) + "m"
def increase_duration(self, instance):
self.duration += 60
self.update_display()
def decrease_duration(self, instance):
if self.duration > 60:
self.duration -= 60
self.update_display()
def start_timer(self, *instance):
app = App.get_running_app()
app.root.state_machine = StateMachine(
self.manager,
self.duration,
)
app.root.state_machine.start()
class TimerScreen(Screen):
remaining_time = NumericProperty(0)
duration = NumericProperty(0)
timer_running = BooleanProperty(False)
what = StringProperty("")
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
def __init__(self, **kwargs):
super(TimerScreen, self).__init__(**kwargs)
self.leave_is_stop = True
self.alarm = SoundLoader.load("/sdcard/Alarms/alarm.ogg")
if self.alarm is None:
raise Exception("Alarm could not be loaded")
with self.canvas.before:
self.color = Color(0, 1, 1, 1)
self.rect = Rectangle(size=self.size, pos=self.pos)
self.bind(size=self._update_rect, pos=self._update_rect)
layout = GridLayout(cols=1)
self.progress = ProgressBar(size_hint=(1, 0.5))
layout.add_widget(self.progress)
self.time_label = Label(text="", font_size='50sp', color=(0, 0, 0, 1))
layout.add_widget(self.time_label)
self.what_label = Label(text="", font_size='20sp', color=(0, 0, 0, 1))
layout.add_widget(self.what_label)
button_layout = BoxLayout()
self.pause_button = Button(text="Pause",
background_color=(0, 0, 1, 1),
color=(1, 1, 1, 1))
self.pause_button.bind(on_press=self.toggle_clock)
button_layout.add_widget(self.pause_button)
layout.add_widget(button_layout)
self.add_widget(layout)
self.bind(on_pre_enter=self.pre_enter_handler)
self.bind(on_pre_leave=self.pre_leave_handler)
with WakeLock("simpletimer", wakeup=True):
self.keep_screen_on()
def keep_screen_on(self, revert=False):
from android import mActivity
from jnius import autoclass
from android.runnable import run_on_ui_thread
LayoutParams = autoclass('android.view.WindowManager$LayoutParams')
@run_on_ui_thread
def do():
if revert:
logger.debug("Leaving screen always on")
mActivity.getWindow().clearFlags(LayoutParams.FLAG_KEEP_SCREEN_ON)
else:
logger.debug("Entering screen always on")
mActivity.getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON)
do()
time.sleep(0.5)
def pre_leave_handler(self, *args):
self.stop_clock()
if self.alarmtime_clock:
self.alarmtime_clock.cancel()
self.alarmtime_clock = None
self.keep_screen_on(revert=True)
if self.alarm:
self.alarm.stop()
self.alarm = None
from jnius import autoclass
from android import mActivity
View = autoclass('android.view.View')
option = View.SYSTEM_UI_FLAG_VISIBLE
@run_on_ui_thread
def fs():
logger.debug("Leaving fullscreen mode")
mActivity.getWindow().getDecorView().setSystemUiVisibility(option)
fs()
def pre_enter_handler(self, *args):
self.keep_screen_on()
from jnius import autoclass
from android import mActivity
View = autoclass('android.view.View')
option = View.SYSTEM_UI_FLAG_FULLSCREEN
@run_on_ui_thread
def fs():
logger.debug("Entering fullscreen mode")
mActivity.getWindow().getDecorView().setSystemUiVisibility(option)
fs()
def start_timer(
self,
what,
color,
duration,
on_done,
sound_map,
):
self.wanted_color = color
self.color.rgb = self.wanted_color
App.get_running_app().goto("simpletimer:timer")
self.duration = duration
self.what = what
self.elapsed_alarm_time = 0.
self.duration_alarm_fade_in = 90.
self.alarmtime_clock = None
self.remaining_time = duration
self.on_done = on_done
self.finished = False
self.sound_map = sound_map
self.sound = self.sound_map.get(None)
if self.sound:
self.sound.play()
self.start_clock()
def update_display(self):
self.what_label.text = self.what
if self.clock:
self.color.rgb = self.wanted_color
self.pause_button.text = "Pause"
else:
if self.finished:
self.what_label.text = "C'est fini !!!"
self.pause_button.text = "Ok"
else:
self.color.rgb = orange
self.pause_button.text = "Continue ?"
self.time_label.text = str(self.remaining_time)
self.progress.max = self.duration
self.progress.value = self.remaining_time
def countdown(self, dt):
if self.remaining_time > 0:
self.remaining_time -= 1
else:
self.stop_clock()
self.alarmtime_clock = Clock.schedule_interval(self.alarmtime, 1)
self.alarm.loop = True
self.alarm.volume = 0
self.color.rgb = (0, 0, 0)
self.alarm.play()
self.finished = True
self.update_display()
new_sound = self.sound_map.get(self.remaining_time)
if new_sound:
if self.sound:
self.sound.stop()
self.sound = new_sound
self.sound.play()
def alarmtime(self, *_):
self.elapsed_alarm_time += 1
self.time_label.text = str(int(self.elapsed_alarm_time))
if self.elapsed_alarm_time < self.duration_alarm_fade_in:
normalized_time = self.elapsed_alarm_time / self.duration_alarm_fade_in
vol = math.sin(normalized_time * math.pi / 2)
self.color.rgb = (vol,) * 3
logger.debug("Alarm volume: {}".format(vol))
self.alarm.volume = vol
else:
self.alarm.volume = 1.0
def start_clock(self):
self.clock = Clock.schedule_interval(self.countdown, 1)
self.update_display()
def stop_clock(self):
Clock.unschedule(self.clock)
self.clock = None
self.update_display()
def toggle_clock(self, instance=None):
if self.finished:
self.leave()
elif self.clock:
self.stop_clock()
else:
self.start_clock()
def finish(self):
self.finished = True
def leave(self, instance=None):
if self.leave_is_stop:
to_service("exit")
import time
time.sleep(2)
App.get_running_app().stop()
else:
App.get_running_app().back()
class SimpleTimerApp(App):
def goto(self, screen):
self.manager.current = screen
def back(self):
self.manager.current = 'simpletimer:settings'
@staticmethod
def populate(sm):
try:
sm.get_screen('simpletimer:settings')
return
except ScreenManagerException:
pass # not populated yet
sm.add_widget(SettingsScreen(name='simpletimer:settings'))
sm.add_widget(TimerScreen(name='simpletimer:timer'))
def build(self):
sm = ScreenManager()
self.populate(sm)
return sm
def run():
SimpleTimerApp().run()