Cat

Kawai.Focus - приложение для фокусировки внимания (часть 8)

Данная статья посвящена:

  • Работе с фреймворком Kivy в проекте Kawai.Focus;
  • Созданию валидации в конструкторе таймера;
  • Написанию кода для автоматического расчёта часов, минут и секунд из введённых минут;
  • Обновлению кода.

Дополнительные материалы

Icon Link

Реклама

Icon Link
Kawai.Focus Arduinum628 10 Июль 2025 Просмотров: 42

Вступление

Всем доброго дня! В предыдущей статье 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-таймера. Также выведу дополнительную информацию на экран таймера — например, его название, которое сейчас не используется.

На самом деле я хотел реализовать цепочку таймеров в текущей статье, но из-за объёма изменений и количества информации она бы сюда просто не поместилась.

Если у вас есть мысли о том, как можно улучшить проект, пишите в комментариях — с удовольствием ознакомлюсь с вашими предложениями!

Читайте продолжение — не пропустите!


Заключение

  1. Написан код для подсчёта часов, минут и секунд из введённых минут;
  2. Написана и проверена валидация для конструктора таймера;
  3. Обновлён код.

Ссылки к статье

Автор

    Нет комментариев

    Реклама