
Kawai.Focus - приложение для фокусировки внимания (часть 8)
Данная статья посвящена:
- Работе с фреймворком Kivy в проекте Kawai.Focus;
- Созданию валидации в конструкторе таймера;
- Написанию кода для автоматического расчёта часов, минут и секунд из введённых минут;
- Обновлению кода.
Дополнительные материалы

Для скачивания материалов необходимо войти или зарегистрироваться
Файлы также можно получить в Telegram-боте по коду: 984982
Реклама

Вступление
Всем доброго дня! В предыдущей статье Kawai.Focus - приложение для фокусировки внимания (часть 7) было сделано:
- Написана демонстрационная версия конструктора таймера;
- Реализован переход между экранами;
- Обновлён код таймера для работы с новыми данными;
- Продемонстрировано создание нового таймера в двух сценариях:
- Таймер успешно создан;
- Таймер не удалось создать.
Я написал много функционала, который не был закончен до конца.
Сегодня я добавлю валидацию для полей формы конструктора таймера. Пользователь будет видеть, какие поля мешают созданию таймера, и приложение больше не позволит создавать таймер с пустыми или некорректными данными.
Также я обновлю часть кода и добавлю функцию, которая преобразует количество минут в часы, минуты и секунды.
Заваривайте чай, доставайте вкусняшки — пора “обрабатывать помидоры от вредителей”! 🍅
Подсчёт времени из минут
Текущий код поддерживает работу с таймером только в пределах 59 минут. Это связано с тем, что в нём не реализован полный расчёт времени — используется временное решение, основанное исключительно на работе с минутами.
Часть старого кода класса TimerConstructorScreen
из файла timer_constructor_screen.py
:
# Другой код
# Todo: временное решение: нужно сделать механизм для
# автоматического рассчёта часов, минут и секунд из
# колличества минут, введённого пользователем
screen_timer.ids.time_label.text = f'00:{
"0" + str(timer.pomodoro_time) if timer.pomodoro_time <= 9 else timer.pomodoro_time
}:00'
# Другой код
Функция calculate_time()
Для того что-бы задействовать в коде часы и секунды я напишу функцию calculate_time()
в utils/utils.py
, которая будет подсчитывать часы, минуты и секунды из введённых пользователем минут и возвращать строку 00:00:00
вида при создании таймера.
Код функции:
def calculate_time(mm_user: int) -> str:
"""Функция для подсчёта часов, минут и секунд из минут"""
valid_data = None
hh = mm_user // 60
mm = mm_user % 60
if hh 0:
valid_data = TimerTimeModel(mm=mm)
else:
valid_data = TimerTimeModel(hh=hh, mm=mm)
return (
f"{'0' + str(valid_data.hh) if valid_data.hh <= 9 else valid_data.hh}:"
f"{'0' + str(valid_data.mm) if valid_data.mm <= 9 else valid_data.mm}:00"
)
Разбор кода:
- Назначение функции: Преобразовать количество минут (
mm_user
) в строку форматаhh:mm:ss
; - Вычисление часов и минут: Определяет
hh
иmm
через деление и остаток от деления; - Создание модели времени: Создаёт экземпляр
TimerTimeModel
, содержащийhh
иmm
; - Форматирование вывода: Возвращает строку времени с ведущими нулями:
"hh:mm:00"
; - Условие для часов: Если часы равны нулю, используются только минуты.
Секунды в классе TimerTimeModel
больше не нужны поэтому я их удалил строки кода с ними из TimerTimeModel
. Теперь он выглядит так:
class TimerTimeModel(BaseModel):
"""Модель схемы данных времени таймера"""
hh: int = Field(0, ge=0, le=23)
mm: int = Field(0, ge=0, le=59)
@field_validator('hh', 'mm')
@classmethod
def check_all_time_fields(cls, value: int) -> int:
"""Метод валидирует все поля времени"""
# гарантирует, что время не равно 00:00:00
if value 0:
raise ValueError(ErrorMessage.NO_TIME.value)
return value
Применение функции в классе TimerConstructorScreen
:
# Другой код
if timer:
screen_timer = self.manager.get_screen('timers_screen')
screen_timer.timer = timer
time_culc = calculate_time(mm_user=timer.pomodoro_time)
screen_timer.timer_start_time = time_culc
screen_timer.ids.time_label.text = time_culc
# Другой код
Разбор нового кода:
- Вычисление времени запуска: Вызывает
calculate_time()
для получения строки формата"hh:mm:ss"
по длительности помидоро-сессии; - Установка начального времени: Задаёт
timer_start_time
и текст метки времени (time_label.text
) на экране таймера; - Подготовка генератора таймера: Инициализирует
screen_timer.timer_generator
с помощьюcustom_timer()
на основе значенияpomodoro_time
.
Упрощение функции custom_timer()
Старая функция работала нормально, но содержала много лишнего, поэтому я решил её упростить:
- Обработка ошибок оказалась избыточной, так как теперь пользователь просто не сможет ввести некорректные данные. Следует стремиться писать код так, чтобы ошибки не возникали вовсе;
- Расчёт количества секунд по нескольким полям модели создаёт лишние математические операции — для вычисления достаточно количества минут, введённых пользователем.
def custom_timer(valid_data: TimerTimeModel) -> Generator[str, None, None]:
"""Функция отсчитывает время, установленное для таймера в формате
'hh:mm:ss'. Возвращает генератор, который возвращает текущее время
в формате 'hh:mm:ss'."""
try:
total_seconds = (int(valid_data.hh) * 3600) + (int(valid_data.mm) * 60) + (int(valid_data.ss))
for remaining in range(total_seconds, -1, -1):
yield f"{remaining // 3600:02d}:{(remaining % 3600) // 60:02d}:{remaining % 60:02d}"
except (TypeError, ValueError) as err:
Logger.error(f'Logger: {err.__class__.__name__}: {err}')
Упрощённая функция:
def custom_timer(mm_user: int) -> Generator[str, None, None]:
"""Функция отсчитывает время, установленное для таймера в формате
'hh:mm:ss'. Возвращает генератор, который возвращает текущее время
в формате 'hh:mm:ss'."""
total_seconds = mm_user * 60
for remaining in range(total_seconds, -1, -1):
yield f"{remaining // 3600:02d}:{(remaining % 3600) // 60:02d}:{remaining % 60:02d}"
Разбор обновлённого кода:
custom_timer(mm_user: int)
— теперь функция принимает минуты, введённые пользователем;total_seconds = mm_user * 60
— расчёт секунд из минут.
Применение обновлённой функции в классе TimerConstructorScreen
:
# Другой код
screen_timer.timer_generator = custom_timer(mm_user=timer.pomodoro_time)
self.manager.current = 'timers_screen'
# Другой код
Разбор обновлённого кода:
screen_timer.timer_generator = custom_timer(mm_user=timer.pomodoro_time)
— сейчас генератор передаётся вTimerScreen
изTimerConstructorScreen
.
Обновление класса TimerScreen
На данный момент я много обновил кода и изменил передачу данных в класс TimerScreen
, поэтому он нуждается в рефакторинге.
Метод init()
# Другой код
self.timer_start_time = None
# Другой код
self.timer_start_time = None
— в эту строку будет передано стартовое время из экрана конструктора.
Метод start_timer()
Данный метод запускает таймер. Из него я удалю пару лишних строк, функционал которых выполняет конструктор таймера при передаче в экран таймера.
else:
# Инициализация генератора таймера
self.valid_timer_data = TimerTimeModel(mm=self.timer.pomodoro_time)
self.timer_generator = custom_timer(valid_data=self.valid_timer_data)
self.remaining_time = next(self.timer_generator, self.zero_time)
Обновлённый код:
# Другой код
else:
# Инициализация генератора таймера
self.remaining_time = next(self.timer_generator, self.zero_time)
# Другой код
Метод stop_timer()
Данный метод останавливает таймер. Тут мне нужно обновить устаревшие строки, а сами названия переменных останутся нетронутыми.
self.remaining_time = TimerTimeModel(mm=self.timer.pomodoro_time).mm
# Todo: временное решение: нужно сделать механизм для
# автоматического рассчёта часов, минут и секунд из
# колличества минут, введённого пользователем
self.ids.time_label.text = f'00:{
"0" + str(self.remaining_time) if self.remaining_time <= 9 else self.remaining_time
}:00'
Обновлённый код:
# Другой код
self.remaining_time = next(self.timer_generator, self.zero_time)
self.ids.time_label.text = self.timer_start_time
# Другой код
Разбор кода:
self.remaining_time = next(self.timer_generator, self.zero_time)
— инициация генератора теперь такая же как и в методе старта;self.ids.time_label.text = self.timer_start_time
— использую строку старта таймера, переданную из экрана конструктора.
Полный код класса TimerScreen
from kivy.uix.screenmanager import Screen
from kivy.clock import Clock
from kivy.core.audio import SoundLoader
from kawai_focus.utils.utils import data_json
class TimerScreen(Screen):
"""Экран таймера"""
sound_timer_name = data_json.get_text('sound_timer')
path_file = f'sounds/{sound_timer_name}'
sound = SoundLoader.load(path_file)
def __init__(self, **kwargs):
super(TimerScreen, self).__init__(**kwargs)
# Переменные для управления таймером
self.zero_time = data_json.get_text('zero_time')
self.timer_generator = None
self.paused = False
self.remaining_time = None
self.sound_stop_event = None
self.timer = None
self.timer_start_time = None
def start_timer(self, instance) -> None:
"""Метод для запуска таймера"""
if self.paused:
self.paused = False
else:
# Инициализация генератора таймера
self.remaining_time = next(self.timer_generator, self.zero_time)
# Запуск обновления времени каждую секунду
Clock.schedule_interval(self.update_time, 1)
def pause_timer(self, instance) -> None:
"""Метод для паузы таймера"""
if not self.paused:
self.paused = True
# Остановка обновления времени
Clock.unschedule(self.update_time)
def stop_timer(self, instance) -> None:
"""Метод для остановки таймера"""
# Остановка обновления времени
Clock.unschedule(self.update_time)
self.paused = False
self.remaining_time = next(self.timer_generator, self.zero_time)
self.ids.time_label.text = self.timer_start_time
self.sound.stop()
if self.sound_stop_event:
Clock.unschedule(self.sound_stop_event)
self.sound_stop_event = None
def play_sound(self, dt) -> None:
"""Метод для воспроизведения звука"""
self.sound.play()
# Планируем остановку звука через 20 секунд
self.sound_stop_event = Clock.schedule_once(lambda dt: self.sound.stop(), 20)
def update_time(self, dt) -> None:
"""Метод для обновления времени на экране"""
if self.paused:
return
# Получение следующего значения времени из генератора
self.remaining_time = next(self.timer_generator, self.zero_time)
self.ids.time_label.text = self.remaining_time
if self.remaining_time self.zero_time:
# Остановка обновления времени
Clock.unschedule(self.update_time)
Clock.schedule_once(self.play_sound)
Валидация конструктора таймера
В прошлой статье я показывал пример работы, при котором приложение функционировало некорректно. В моём случае таймер был создан с отрицательным значением времени и пустой строкой в качестве названия. После запуска такого таймера сработала валидация, которая не допустила отрицательное значение, и приложение аварийно завершило работу с ошибкой ValidationError
в поле mm
(минуты).
Как должна себя вести программа в подобных случаях:
- Не создавать таймер с некорректными данными в базе данных;
- Сообщать пользователю, что он не ввёл название в поле
title
; - Не допускать ввод отрицательных чисел и текста в поля, предназначенные для чисел;
- Не позволять пользователю сделать число отрицательным с помощью кнопок счётчика.
Обновление timer_constructor_screen.kv
Для вывода ошибки для title
поля я использую Label
под ним, в который я вставлю текст ошибки когда она появится.
Label:
id: title_error
text: ""
color: (1, 0.3, 0.3, 1)
font_size: 14
size_hint_y: None
height: 20
pos_hint: {"center_x": 0.5, "center_y": 0.8}
Разбор новых строк:
text: ""
— пустая строка, в которую будет передаваться сообщение из Python;color: (1, 0.3, 0.3, 1)
— задаёт красный цвет для текста;font_size: 14
— размер шрифта.
Мне также нужно сделать поля для ввода чисел неактивными, чтобы пользователь не мог ввести обычный текст в числовое поле.
disabled: True
Строка disabled: True
в KV-разметке делает виджет неактивным — пользователь не сможет с ним взаимодействовать, например, вводить текст или нажимать. Этим я избегаю дополнительную валидацию и экономлю строки кода.
Весь timer_constructor_screen.kv
#:kivy 2.3.1
<TimerConstructorScreen>:
FloatLayout:
# поле ввода "название"
TextInput:
id: title
size_hint: None, None
size: 220, 30
pos_hint: {"center_x": 0.5, "center_y": 0.9}
hint_text: "название"
Label:
id: title_error
text: ""
color: (1, 0.3, 0.3, 1)
font_size: 14
size_hint_y: None
height: 20
pos_hint: {"center_x": 0.5, "center_y": 0.8}
# числовой счётчик "время помидора"
Label:
text: "помидор"
size_hint: None, None
pos_hint: {"center_x": 0.3, "center_y": 0.7}
Button:
text: "-"
size_hint: None, None
size: 40, 40
pos_hint: {"center_x": 0.5, "center_y": 0.7}
on_press: time_tomato_input.decrement()
TimeTomatoInput:
id: time_tomato_input
disabled: True
size_hint: None, None
size: 40, 30
pos_hint: {"center_x": 0.6, "center_y": 0.7}
Button:
text: "+"
size_hint: None, None
size: 40, 40
pos_hint: {"center_x": 0.7, "center_y": 0.7}
on_press: time_tomato_input.increment()
# числовой счётчик "время прерыва"
Label:
text: "перерыв"
size_hint: None, None
pos_hint: {"center_x": 0.3, "center_y": 0.6}
Button:
text: "-"
size_hint: None, None
size: 40, 40
pos_hint: {"center_x": 0.5, "center_y": 0.6}
on_press: time_break_input.decrement()
TimeBreakInput:
id: time_break_input
disabled: True
size_hint: None, None
size: 40, 30
pos_hint: {"center_x": 0.6, "center_y": 0.6}
Button:
text: "+"
size_hint: None, None
size: 40, 40
pos_hint: {"center_x": 0.7, "center_y": 0.6}
on_press: time_break_input.increment()
# числовой счётчик "время длительного перерыва"
Label:
text: "перерывище"
size_hint: None, None
pos_hint: {"center_x": 0.3, "center_y": 0.5}
Button:
text: "-"
size_hint: None, None
size: 40, 40
pos_hint: {"center_x": 0.5, "center_y": 0.5}
on_press: time_long_break_input.decrement()
TimeLoongBreakInput:
id: time_long_break_input
disabled: True
size_hint: None, None
size: 40, 30
pos_hint: {"center_x": 0.6, "center_y": 0.5}
Button:
text: "+"
size_hint: None, None
size: 40, 40
pos_hint: {"center_x": 0.7, "center_y": 0.5}
on_press: time_long_break_input.increment()
# числовой счётчик "количество помидоров"
Label:
text: "помидоров"
size_hint: None, None
pos_hint: {"center_x": 0.3, "center_y": 0.4}
Button:
text: "-"
size_hint: None, None
size: 40, 40
pos_hint: {"center_x": 0.5, "center_y": 0.4}
on_press: count_tomatos_input.decrement()
CountTomatosInput:
id: count_tomatos_input
disabled: True
size_hint: None, None
size: 40, 30
pos_hint: {"center_x": 0.6, "center_y": 0.4}
Button:
text: "+"
size_hint: None, None
size: 40, 40
pos_hint: {"center_x": 0.7, "center_y": 0.4}
on_press: count_tomatos_input.increment()
# Кнопка "Создать"
Button:
text: 'Создать'
size_hint: None, None
height: 40
width: 100
pos_hint: {'center_x': 0.7, 'center_y': 0.1}
on_press: root.create_timer(self)
# Кнопка "Назад"
Button:
text: 'Назад'
size_hint: None, None
height: 40
width: 100
pos_hint: {'center_x': 0.3, 'center_y': 0.1}
on_press: root.back(self)
Валидатор поля title
Я решил создать в пакете screens
отдельный файл validators_fields.py
, в котором будут храниться валидаторы для полей экранов.
def validate_title(obj, text):
"""Валидатор поля title"""
if not text:
obj.ids.title_error.text = 'Введите название!'
Разбор кода:
- Назначение функции: Проверяет, введено ли название в поле
title
; - Проверка текста: Если значение
text
пустое — считается ошибкой; - Вывод сообщения об ошибке: Устанавливает текст
'Введите название!'
вtitle_error
, чтобы пользователь увидел предупреждение.
Применение валидатора в классе TimerConstructorScreen
:
# Другой код
def create_timer(self, instance):
"""Метод для создания таймера"""
validate_title(self, self.ids.title.text)
# прекратить создание таймера если поле пустое
if not self.ids.title.text:
return
# Другой код
Разбор обновлённого кода:
validate_title(self, self.ids.title.text)
— вызов валидатора и передача в него поля значение из поляtitle
;if not self.ids.title.text:
— если полеtitle
пустое, метод завершает выполнение черезreturn
.
Полный код класса TimerConstructorScreen
from kivy.uix.screenmanager import Screen
from kawai_focus.custom_widgets.timer_wigets import TimeTomatoInput
from kawai_focus.schemas import TimerModel
from kawai_focus.database.cruds import new_timer
from kawai_focus.utils.utils import calculate_time, custom_timer
from kawai_focus.screens.validators_fields import validate_title
class TimerConstructorScreen(Screen):
"""Экран конструктора таймера"""
def __init__(self, **kwargs):
super(TimerConstructorScreen, self).__init__(**kwargs)
def create_timer(self, instance):
"""Метод для создания таймера"""
validate_title(self, self.ids.title.text)
# прекратить создание таймера если поле пустое
if not self.ids.title.text:
return
timer = new_timer(
data=TimerModel(
title=self.ids.title.text,
pomodoro_time=int(self.ids.time_tomato_input.text),
break_time=int(self.ids.time_break_input.text),
break_long_time=int(self.ids.time_long_break_input.text),
count_pomodoro=int(self.ids.count_tomatos_input.text)
)
)
if timer:
screen_timer = self.manager.get_screen('timers_screen')
screen_timer.timer = timer
time_culc = calculate_time(mm_user=timer.pomodoro_time)
screen_timer.timer_start_time = time_culc
screen_timer.ids.time_label.text = time_culc
screen_timer.timer_generator = custom_timer(mm_user=timer.pomodoro_time)
self.manager.current = 'timers_screen'
def back(self, instance) -> None:
"""Метод для кнопки назад - возврат в меню таймеров"""
pass
Счётчик количества
Теперь мне нужно обновить код в счётчиках для полей ввода количества. Я решил установить ограничения в соответствии с рекомендациями по настройке Pomodoro-таймера. Это повысит эффективность работы таймера для пользователя и не позволит задать слишком большие или слишком маленькие значения.
Изменение базового класса BaseNumInput
Я начну с изменения базового класса. Дело в том, что методы increment
и decrement
теперь будут иметь различное поведение в классах-потомках. Поэтому классическое наследование этих методов утратило смысл и перестало быть актуальным с точки зрения сокращения кода и удобства.
Старый код базового класса:
class BaseNumInput(TextInput):
"""Базовый класс для поля числа"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.text = '0'
self.halign = 'center'
def increment(self):
"""Метод для прибавки 1"""
self.text = str(int(self.text) + 1)
def decrement(self):
"""Метод для вычитания 1"""
self.text = str(int(self.text) - 1)
Мне нужен класс, который будет указывать на обязательную реализацию методов increment
и decrement
в классах-потомках.
Какие есть альтернативы:
- Использовать абстрактный класс на базе
ABC
; - Создать обычный класс, в котором методы
increment
иdecrement
вызываютNotImplementedError
.
Изначально я хотел использовать абстрактный класс, но он оказался несовместим с классами Kivy (TypeError
). Поэтому я выбрал второй вариант.
class BaseNumBehavior:
"""Псевдо-интерфейс для числового ввода"""
def increment(self):
raise NotImplementedError('Метод increment() должен быть реализован')
def decrement(self):
raise NotImplementedError('Метод decrement() должен быть реализован')
Разбор кода:
- Назначение класса:
BaseNumBehavior
выступает как псевдо-интерфейс, задающий контракт для числового ввода; - Метод
increment
: Обозначен как обязательный к реализации — при вызове без переопределения выбрасываетNotImplementedError
; - Метод
decrement
: Также требует реализации в подклассах и выбрасывает исключение при отсутствии переопределения; - Смысл применения: Подходит для использования в сочетании с виджетами Kivy, когда нужно обеспечить наличие числовых операций, не прибегая к абстрактным классам (из-за несовместимости метаклассов).
Проверка работы класса
Закомментирую один из методов класса:
class TimeBreakInput(TextInput, BaseNumBehavior):
"""Класс для поля ввода времени перерыва"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.text = '5'
self.halign = 'center'
# def increment(self):
# """Метод для прибавки 1"""
# if int(self.text) < 10:
# self.text = str(int(self.text) + 1)
def decrement(self):
"""Метод для вычитания 1"""
if int(self.text) > 3:
self.text = str(int(self.text) - 1)
Если использовать конструктор, то получаю ошибку:
NotImplementedError: Метод increment() должен быть реализован
Этот механизм гарантирует, что метод будет реализован в классе потомке.
Класс TimeTomatoInput
Данный класс нужен для ввода количества помидоров.
class TimeTomatoInput(TextInput, BaseNumBehavior):
"""Класс для поля ввода количества помидоров"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.text = '25'
self.halign = 'center'
def increment(self):
"""Метод для прибавки 1"""
if int(self.text) < 90:
self.text = str(int(self.text) + 1)
def decrement(self):
"""Метод для вычитания 1"""
if int(self.text) > 10:
self.text = str(int(self.text) - 1)
Разбор нового кода:
if int(self.text) < 90:
в методеincrement()
— условие прибавки 1 если число меньше 90 (полтора часа);if int(self.text) > 10:
— условие убавления 1 если число больше 10 (минуты).
Класс TimeBreakInput
Назначение этого класса — ввод времени перерыва между "помидорами".
class TimeBreakInput(TextInput, BaseNumBehavior):
"""Класс для поля ввода времени перерыва"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.text = '5'
self.halign = 'center'
def increment(self):
"""Метод для прибавки 1"""
if int(self.text) < 10:
self.text = str(int(self.text) + 1)
def decrement(self):
"""Метод для вычитания 1"""
if int(self.text) > 3:
self.text = str(int(self.text) - 1)
Разбор нового кода:
if int(self.text) < 10:
в методеincrement()
— условие прибавки 1 если число меньше 10 (минуты);if int(self.text) > 3:
— условие убавления 1 если число больше 13 (минуты).
Класс TimeLoongBreakInput
Данный класс нужен для ввода длительного перерыва.
class TimeLoongBreakInput(TextInput, BaseNumBehavior):
"""Класс для поля ввода времени длительного перерыва"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.text = '15'
self.halign = 'center'
def increment(self):
"""Метод для прибавки 1"""
if int(self.text) < 40:
self.text = str(int(self.text) + 1)
def decrement(self):
"""Метод для вычитания 1"""
if int(self.text) > 15:
self.text = str(int(self.text) - 1)
Разбор нового кода:
if int(self.text) < 40:
в методеincrement()
— условие прибавки 1 если число меньше 40 (минуты);if int(self.text) > 15:
— условие убавления 1 если число больше 15 (минуты).
Класс CountTomatosInput
Назначение класса — ввод количества "помидоров".
class CountTomatosInput(TextInput, BaseNumBehavior):
"""Класс для поля ввода количества помидоров"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.text = '4'
self.halign = 'center'
def increment(self):
"""Метод для прибавки 1"""
if int(self.text) < 8:
self.text = str(int(self.text) + 1)
def decrement(self):
"""Метод для вычитания 1"""
if int(self.text) > 2:
self.text = str(int(self.text) - 1)
Разбор нового кода:
if int(self.text) < 8:
в методеincrement()
— условие прибавки 1 если число меньше 8 (шт);if int(self.text) > 2:
— условие убавления 1 если число больше 2 (шт).
Работа приложения
Проверка валидатора поля title
Запускаю приложение в terminal:
poetry run kawai-focus
Не ввожу название и нажимаю кнопку "Создать":

При попытке создания таймера без названия появляется предупреждение "Введите название!", и новая запись не создаётся. Это делает проблему понятной для пользователя и защищает базу данных от некорректных данных:

Проверка создания нового таймера
Создаю таймер с максимально большими настройками:

Кнопка "+" теперь не позволяет вводить значения, превышающие допустимый предел для каждого поля. Запредельно большие значения таймера больше не принимаются. После создания таймера новая запись успешно появляется в базе данных, а пользователь перенаправляется на экран таймера.
Нажимаю на кнопку "Старт" для проверки работы обновления времени:

Обратный отсчёт успешно пошёл:

Создаю второй таймер для проверки минимальных настроек:

Кнопки "–" также успешно ограничивают значения до минимально допустимых, а поле не позволяет вручную вводить значения. При отключении ввода поле становится визуально более тусклым, что дополнительно подчёркивает его неактивное состояние.
Успешно попадаю в новый таймер:

Новый функционал успешно протестирован.
Анонс на следующие статьи
Следующая моя статья выйдет 31 июля 2025 года. В ней я реализую функционал запуска таймеров друг за другом по цепочке, что обеспечит полноценную работу механики Pomodoro-таймера. Также выведу дополнительную информацию на экран таймера — например, его название, которое сейчас не используется.
На самом деле я хотел реализовать цепочку таймеров в текущей статье, но из-за объёма изменений и количества информации она бы сюда просто не поместилась.
Если у вас есть мысли о том, как можно улучшить проект, пишите в комментариях — с удовольствием ознакомлюсь с вашими предложениями!
Читайте продолжение — не пропустите!
Заключение
- Написан код для подсчёта часов, минут и секунд из введённых минут;
- Написана и проверена валидация для конструктора таймера;
- Обновлён код.
Ссылки к статье
- Мои статьи Arduinum628 на Код на салфетке;
- Репозиторий проекта Kawai.Focus.
Оглавление
- Вступление
- Подсчёт времени из минут
- Функцияcalculate_time()
- Упрощение функцииcustom_timer()
- Обновление классаTimerScreen
- Полный код классаTimerScreen
- Валидация конструктора таймера
- Обновлениеtimer_constructor_screen.kv
- Весьtimer_constructor_screen.kv
- Валидатор поля title
- Полный код классаTimerConstructorScreen
- Счётчик количества
- Изменение базового классаBaseNumInput
- Работа приложения
- Проверка валидатора поляtitle
- Проверка создания нового таймера
- Анонс на следующие статьи
- Заключение
- Ссылки к статье
Все статьи