Вступление
Всем доброго дня! В предыдущей статье Kawai.Focus - приложение для фокусировки внимания (часть 12):
- Подключена боковая панель
NavigationRail; - Добавлены новые кнопки на экран «Таймеры».
Сегодня я поменяю дизайн приложения экрана «Таймер» к Material Design виду, который нужен для запуска цепочки таймеров пользователя. Я постараюсь сделать данный экран максимально удобным и информативным для пользователя, а так же убрать лишние кнопки в панель навигации.
Так же у меня возникла неожиданная проблема, с бекендом звука и связанной с ним SDL2 библиотекой, которой не возникало при использовании чистого Kivy без KivyMD. Я вынужденно буду решать её сегодня.
Ещё мне предстоит заняться конвертацией звука.
Заваривайте чай, доставайте вкусняшки — пора “следить за созреванием красивых помидоров и избавить их от вредных колорадских жуков”! 🍅
Проблема запуска двух backends на SDL2
Как только я сделал наследование от MDScreen класса, подключил экран в main.py у меня перестала работать отрисовка окна приложения и всё зависало. Выглядит визуально это следующим образом.

На экране просто появляется полоска от окна с названием приложения и больше ничего. Когда я закомментировал код от загрузки звука приложение загрузилось как обычно. По началу я не понял что произошло так как видимых ошибок не было в логах приложения.
Позже я разобрался, что у меня повисает главный поток пока SDL2 пытается инициализировать аудио.
Что такое SDL2?
SDL2 (Simple DirectMedia Layer) — это кроссплатформенная библиотека, которая предоставляет низкоуровневый доступ к мультимедиа:
- графика (OpenGL/2D);
- звук;
- обработка ввода с клавиатуры, мыши, геймпада;
- таймеры и события.
SDL2 в Kivy:
В Kivy есть несколько компонентов, которые могут использовать SDL2:
- Window backend (
window_sdl2) :
Kivy может рендерить интерфейс через SDL2. Это значит, что графический контекст (окна, OpenGL) создаётся через SDL2; - Audio backend (
audio_sdl2) :
Kivy может воспроизводить звуки через SDL2. При этом используется SDL2 mixer для загрузки и проигрывания аудио; - Ввод :
SDL2 обрабатывает события клавиатуры, мыши и сенсоров.
О баге:
- В KivyMD 2.0.0/2.0.1.dev вызов
SoundLoader.load()блокирует главный поток приложения при использованииaudio_sdl2; - Это происходит даже если звук валидный, без ошибок — приложение просто «виснет»;
- Причина — конфликт инициализации SDL2 аудио с контекстом графики KivyMD;
- Решение — использовать
ffpyplayerдля аудио вместоaudio_sdl2.
Для проверки звука я решил запустить его для проверки из главного приложения в main.py для этого я временно написал функцию и метод, которые помогут быстро протестировать запуск звука.
utils.py
В utils.py я написал функцию, которая вернёт объект с загруженной мелодией.
from kivy.core.audio import SoundLoader, Sound
def load_sound() -> Sound | None:
"""Функция для загрузки звука"""
sound_timer_name = data_json.get_text('sound_timer')
path_file = f'sounds/{sound_timer_name}'
sound = SoundLoader.load(path_file)
return soundНикакого нового кода тут я не написал, а просто скопировал строки кода из класса TimerScreen.
main.py
В главном файле main.py я решил попробовать загружать звук в отдельный поток. Сам по себе отдельный поток не решил проблему, но я попробовал на всякий случай такой вариант.
# Другой код
from kivy.clock import Clock
from utils.utils import load_sound
class KawaiFocusApp(MDApp, MenuApp):
"""Главный класс приложения"""
title = 'Kawai-Focus'
# Другой код
def on_start(self):
# Загружаем звук в отдельном потоке, чтобы не блокировать UI
Thread(target=self.load_sound_timer).start()
def load_sound_timer(self):
try:
sound = load_sound() # твоя функция для получения Sound
if sound:
# Воспроизведение в главном потоке
Clock.schedule_once(lambda dt: sound.play())
# Остановка через 3 секунды
Clock.schedule_once(lambda dt: sound.stop(), 3)
except Exception as err:
print(err)Разбор нового кода:
def on_start(self):— метод, вызываемый при старте приложения; здесь запускается отдельный поток для загрузки звука, чтобы не блокировать UI;Thread(target=self.load_sound_timer).start()— создаётся и запускается поток, выполняющий методload_sound_timer;def load_sound_timer(self):— метод для загрузки и воспроизведения звука:sound = load_sound()— загружается объект звука через пользовательскую функциюload_sound;Clock.schedule_once(lambda dt: sound.play())— воспроизведение звука в главном потоке, безопасно для UI;Clock.schedule_once(lambda dt: sound.stop(), 3)— остановка звука через 3 секунды;try/except— отлавливает и выводит возможные ошибки при загрузке или воспроизведении.
Переход с audio_sdl2 на ffpyplayer
В качестве альтернативы audio_sdl2 воспользуюсь ffpyplayer.
ffpyplayer — это библиотека Python для работы с мультимедиа, основанная на FFmpeg. Она позволяет воспроизводить аудио и видео в приложениях без блокировки главного потока.
Основные характеристики ffpyplayer:
- Асинхронное воспроизведение:
Звук и видео могут загружаться и воспроизводиться в фоновом потоке, не мешая интерфейсу. Это особенно важно для Kivy/KivyMD, где главный поток отвечает за UI; - Поддержка форматов:
Любые форматы, поддерживаемые FFmpeg: mp3, wav, ogg, flac, mp4, mkv и другие; - Высокая производительность:
Использует C-библиотеки FFmpeg для декодирования, что быстрее, чем чисто Python-решения; - Интеграция с Kivy:
Можно использовать вместе сSoundLoader(через бэкендffpyplayer) вместоaudio_sdl2, чтобы избежать подвисаний при загрузке аудио.
Установка в Debian/Ubuntu:
sudo apt install ffmpegДля установки на другие операционные системы есть инструкция на сайте ffmpeg. Есть поддержка Windows и Mac OS.
Добабляю ffpyplayer в проект:
poetry add ffpyplayerfrom kivy import Config
Config.set('kivy', 'audio', 'ffpyplayer')Разбор кода:
from kivy import Config— импортируется объект конфигурации Kivy для настройки параметров приложения;Config.set('kivy', 'audio', 'ffpyplayer')— указывается, что аудиобэкенд Kivy должен использовать ffpyplayer вместо стандартногоaudio_sdl2.
Видео с проверкой звука:
Окно приложения запустилось, а звук воспроизвёлся. Это не может не радовать.
Основные логи ffpyplayer:
[INFO ] [ImageLoaderFFPy] Using ffpyplayer 4.5.3Using ffpyplayer 4.5.3 — говорит о том, что для звука используется новый бекенд для аудио ffpyplayer.
[WARNING] [ffpyplayer ] [mp3 @ 0x7fb86c000d80] Estimating duration from bitrate, this may be inaccurateСообщение [WARNING] переводится как "Оценка длительности по битрейту может быть неточной". Это предупреждение всего лишь информационное и имеет смысл только тогда, когда есть необходимость в точно длине файла, например, в отображении продолжительности или регулировке. В остальном это просто предупреждение, которое ни на что не влияет.
MP3-файлы бывают двух типов:
- CBR (Constant Bitrate) — постоянный битрейт, длительность можно рассчитать точно;
- VBR (Variable Bitrate) — переменный битрейт, и в таких файлах нет точных метаданных о длине.
Когда FFmpeg (а значит и ffpyplayer) не находит эту метаинформацию, он пишет:
“Estimating duration from bitrate, this may be inaccurate”
То есть — «оценил длительность по среднему битрейту, может быть неточно».
FFmpeg — это мощная кроссплатформенная утилита и библиотека для работы с аудио и видео.
Конвертирую .mp3 формат в CBR .mp3 чтобы убрать это предупреждение:
ffmpeg -i alarm.mp3 -b:a 256k -ar 44100 -ac 2 -codec:a libmp3lame -abr 0 alarm_cbr.mp3Разбор команды:
ffmpeg -i alarm.mp3— указывает исходный файлalarm.mp3для обработки;-b:a 256k— задаёт битрейт аудио 256 кбит/с (для постоянного или целевого качества);-ar 44100— устанавливает частоту дискретизации 44,1 кГц;-ac 2— устанавливает количество аудиоканалов (2 — стерео);-codec:a libmp3lame— выбирает кодек MP3libmp3lameдля перекодирования;-abr 0— включает CBR (Constant Bit Rate), т.е. постоянный битрейт;alarm_cbr.mp3— имя выходного файла с новыми параметрами.
Вывод конвертации:
ffmpeg version 5.1.7-0+deb12u1 Copyright (c) 2000-2025 the FFmpeg developers
built with gcc 12 (Debian 12.2.0-14+deb12u1)
configuration: --prefix=/usr --extra-version=0+deb12u1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libdav1d --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libglslang --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librabbitmq --enable-librist --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libsvtav1 --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzimg --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --disable-sndio --enable-libjxl --enable-pocketsphinx --enable-librsvg --enable-libmfx --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libx264 --enable-libplacebo --enable-librav1e --enable-shared
libavutil 57. 28.100 / 57. 28.100
libavcodec 59. 37.100 / 59. 37.100
libavformat 59. 27.100 / 59. 27.100
libavdevice 59. 7.100 / 59. 7.100
libavfilter 8. 44.100 / 8. 44.100
libswscale 6. 7.100 / 6. 7.100
libswresample 4. 7.100 / 4. 7.100
libpostproc 56. 6.100 / 56. 6.100
[mp3 @ 0x5632de7534c0] Estimating duration from bitrate, this may be inaccurate
Input #0, mp3, from 'alarm-beep.mp3':
Duration: 00:00:18.47, start: 0.000000, bitrate: 255 kb/s
Stream #0:0: Audio: mp3, 44100 Hz, mono, fltp, 256 kb/s
Stream mapping:
Stream #0:0 -> #0:0 (mp3 (mp3float) -> mp3 (libmp3lame))
Press [q] to stop, [?] for help
Output #0, mp3, to 'alarm-beep_cbr.mp3':
Metadata:
TSSE : Lavf59.27.100
Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 256 kb/s
Metadata:
encoder : Lavc59.37.100 libmp3lame
size= 579kB time=00:00:18.46 bitrate= 256.7kbits/s speed= 94x
video:0kB audio:578kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.148691%Не буду заострять внимание на процессе конвертации, но скажу, что она была успешной и помогла избавиться от предупреждения.
Новый дизайн экрана «Таймер»
На экран «Таймер» пользователь попадёт нажав на кнопку «Открыть» на кране «Таймеры».

Так выглядел экран «Таймер» в старом дизайне.

Что я хочу видеть в новом дизайне:
- Большой шрифт у цифры таймера, который будет более удобным в отслеживании времени;
- Material Design на KivyMD 2.0.0;
- Кнопка «Пауза» будет появляться только после нажатия на копку старт так же как и кнопка «Стоп». На старом дизайне я забыл это сделать и до старта кнопка «Пауза» не имеет функционального смысла. Так же сделаю уместным появление кнопки «Старт» при нужных ситуациях.
Подбор образца
В образцах из папки examples , которую я взял из репозитория форка KivyMD 2.0.0 нет конкретного экрана под таймер. Зато там есть экран timepicker, который используют для установки времени часов.
Образец timepicker (input):

Что может мне пригодится в образце?
Там есть большой циферблат, который можно использовать для таймера. Достаточно добавить секунды и он будет выглядеть как таймер.
При вызове элемента с настройкой часов блокируются остальные элементы приложения такие как меню бургер. Эту блокировку можно использовать для того чтобы пользователь не отвлекался на остальные элементы и кнопки приложения во время использования таймера.
Вместо кнопок «Cancel» и «Ok» я добавлю кнопки «Стоп» и «Пауза». Остальные ненужные кнопки и надписи такие как «AM» и «PM» я уберу ибо в таймере они не нужны.
В данном случае я буду скорее вдохновляться визуальной частью, и почти не буду использовать код из образца.
timer_screen.kv
Код из .kv файла я переписал основательно поэтому я его покажу целиком. С вёрсткой пришлось повозиться так как очень много отличий от стандартной вёрстки Kivy.
#:kivy 2.3.1
#:include kv/navigation_panel.kv
<TimerScreen>:
NavigationPanel:
id: nav_panel
name: "timer_screen"
MDAnchorLayout:
orientation: "vertical"
anchor_x: "center"
anchor_y: "center"
md_bg_color: app.theme_cls.secondaryContainerColor
# Центральный блок таймера
MDBoxLayout:
orientation: "vertical"
size_hint: None, None
size: "310dp", "200dp"
md_bg_color: app.theme_cls.surfaceColor
radius: [20]
elevation: 4
padding: "16dp"
spacing: "12dp"
# Заголовок блока таймера (слева вверху)
MDBoxLayout:
orientation: "horizontal"
size_hint_y: None
height: "24dp"
spacing: "4dp"
MDLabel:
id: title_label
text: "Таймер"
halign: "left"
valign: "center"
role: "small"
MDLabel:
id: type_timer_label
text: "Помидор"
halign: "right"
valign: "center"
role: "small"
# Крупный таймер
MDLabel:
id: time_label
text: "00:00:00"
halign: "center"
valign: "center"
font_style: "Display"
role: "large"
# Кнопки
MDBoxLayout:
orientation: "horizontal"
size_hint_y: None
height: "48dp"
spacing: "12dp"
pos_hint: {"center_x": .5}
MDButton:
style: "outlined"
id: start_button
on_release: root.start_timer()
theme_line_color: "Custom"
line_color: 0, 1, 0, 1
MDButtonText:
text: "Старт"
MDButton:
style: "outlined"
id: pause_button
on_release: root.pause_timer()
opacity: 0
disabled: True
theme_line_color: "Custom"
line_color: 1, 1, 0, 1
MDButtonText:
text: "Пауза"
MDButton:
style: "outlined"
id: stop_button
on_release: root.stop_timer()
opacity: 0
disabled: True
theme_line_color: "Custom"
line_color: 1, 0, 0, 1
MDButtonText:
text: "Стоп"Разбор .kv файла:
<TimerScreen>— экран с таймером, использующийNavigationPanelдля бокового меню/навигации;MDAnchorLayout— центрирует основной блок таймера на экране, задаёт фон черезsecondaryContainerColor;MDBoxLayout(центральный блок таймера) — вертикальный контейнер с фоном, скруглёнными углами, тенью, внутренним отступом и промежутками между элементами;- Заголовок таймера (
MDBoxLayoutс двумяMDLabel) — слева название «Таймер», справа тип таймера («Помидор»); - Крупный таймер (
MDLabel id: time_label) — показывает текущее время, центрирован, большой шрифт; - Кнопки управления (
MDBoxLayoutсMDButton) — три кнопки:- Старт — зелёная, активная;
- Пауза — жёлтая, изначально скрыта и неактивна;
- Стоп — красная, изначально скрыта и неактивна.
timer_screen.py
Теперь осталось самое сложное переписать логику класса TimerScreen:
- Убрать лишние методы
delete_timer()иback()так как они не будут больше использоваться в данном экране; - Улучшить код загрузки аудио;
- Переписать логику метода
choice_timer()для работы сmatch ... case; - Добавить код для скрытия и появления на экране кнопок управления таймером;
- Написать код, который сделает кнопки боковой панели навигации неактивными во время работы таймера.
Импорты:
from kivymd.uix.screen import MDScreen
from kivy.clock import Clock
from kivy.core.audio import SoundLoader
from os.path import isfile
from kawai_focus.utils.utils import data_json, custom_timer, calculate_time
from kawai_focus.main import LoggerРазбор новых импортов:
from os.path import isfile— импорт функции для проверки, существует ли файл по заданному пути;from kivymd.uix.screen import MDScreen— импорт виджета экрана KivyMD, используется как контейнер для интерфейса;from kivy.clock import Clock— импорт планировщика событий Kivy для запуска функций с задержкой или периодически в главном потоке.
Обновлённый класс TimerScreen:
class TimerScreen(MDScreen):
"""Экран таймера"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self.load_sound)
# Переменные для управления таймером
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
self.source_timer_names = None
self.sound = None # Заглушка
def load_sound(self, dt) -> None:
sound_timer_name = data_json.get_text('sound_timer')
path_file = f'sounds/{sound_timer_name}'
if not isfile(path_file):
Logger.error(f'[TimerScreen] Файл звука не найден: {path_file}')
return
self.sound = SoundLoader.load(path_file)
if not self.sound:
Logger.error(f'[TimerScreen] Не удалось загрузить файл звука (неподдерживаемый формат?): {path_file}')
return
def on_pre_enter(self, *args) -> None:
"""Вызывается перед появлением экрана"""
# Прячем кнопки "Стоп" и "Пауза"
self.ids.stop_button.opacity = 0
self.ids.stop_button.disabled = True
self.ids.pause_button.opacity = 0
self.ids.pause_button.disabled = True
# Проверяем состояние state_machine
if not hasattr(self.manager, 'state_machine') or self.manager.state_machine is None:
self.manager.state_machine = []
def choice_timer(self) -> None:
"""Выбор таймера (помидор / перерыв / длинный перерыв)"""
if not self.manager.state_machine:
return
current_timer_name = self.manager.state_machine.pop(0)
match current_timer_name:
case 'pomodoro':
self.timer_generator = custom_timer(mm_user=self.timer.pomodoro_time)
self.ids.time_label.text = calculate_time(self.timer.pomodoro_time)
self.ids.type_timer_label.text = 'Помидор'
case 'break':
self.timer_generator = custom_timer(mm_user=self.timer.break_time)
self.ids.time_label.text = calculate_time(self.timer.break_time)
self.ids.type_timer_label.text = 'Перерыв'
case _:
self.timer_generator = custom_timer(mm_user=self.timer.break_long_time)
self.ids.time_label.text = calculate_time(self.timer.break_long_time)
self.ids.type_timer_label.text = 'Перерывище'
def start_timer(self, *args) -> None:
"""Запуск таймера"""
# Делаем кнопки панели навигации неактивными
self.ids.nav_panel.ids.menu.disabled = True
self.ids.nav_panel.ids.timers_nav.disabled = True
self.ids.nav_panel.ids.guide_nav.disabled = True
self.ids.nav_panel.ids.info_nav.disabled = True
# Активируем кнопки "Стоп" и "Пауза"
self.ids.stop_button.opacity = 1
self.ids.stop_button.disabled = False
self.ids.pause_button.opacity = 1
self.ids.pause_button.disabled = False
# Прячем кнопку "Старт"
self.ids.start_button.opacity = 0
self.ids.start_button.disabled = True
if self.paused:
self.paused = False
else:
if len(self.manager.state_machine) and self.timer_generator is None:
self.choice_timer()
self.remaining_time = next(self.timer_generator, self.zero_time)
Clock.schedule_interval(self.update_time, 1)
def pause_timer(self, *args) -> None:
"""Пауза таймера"""
if not self.paused:
self.paused = True
Clock.unschedule(self.update_time)
# Активируем кнопку "Старт"
self.ids.start_button.opacity = 1
self.ids.start_button.disabled = False
# Прячем кнопку "Пауза"
self.ids.pause_button.opacity = 0
self.ids.pause_button.disabled = True
def stop_timer(self, *args) -> 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
# Останавливаем звук, если играет
if self.sound:
self.sound.stop()
if self.sound_stop_event:
Clock.unschedule(self.sound_stop_event)
self.sound_stop_event = None
# Возврат цикла таймеров
if not len(self.manager.state_machine):
self.manager.state_machine = self.source_timer_names.copy()
# Делаем кнопки панели навигации активными
self.ids.nav_panel.ids.menu.disabled = False
self.ids.nav_panel.ids.timers_nav.disabled = False
self.ids.nav_panel.ids.guide_nav.disabled = False
self.ids.nav_panel.ids.info_nav.disabled = False
# Прячем кнопки "Стоп" и "Пауза"
self.ids.stop_button.opacity = 0
self.ids.stop_button.disabled = True
self.ids.pause_button.opacity = 0
self.ids.pause_button.disabled = True
# Активируем кнопку "Старт"
self.ids.start_button.opacity = 1
self.ids.start_button.disabled = False
self.choice_timer()
def play_sound(self, *args) -> None:
"""Воспроизведение звука"""
if self.sound:
self.sound.play()
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)Разбор обновлённого кода:
Clock.schedule_once(self.load_sound)— планирует однократный вызов методаload_soundпосле старта экрана; выполняется в главном потоке, безопасно для UI; используется для предварительной загрузки звука без блокировки интерфейса;if not isfile(path_file):— проверка существования файла звука. Если файла нет, выводится ошибка черезLogger.errorи загрузка не выполняется;if not self.sound:— проверка успешной загрузки аудио черезSoundLoader. Если звук не поддерживается или произошла ошибка, выводится сообщение об ошибке;- Примеры скрытия/появления кнопок:
- Скрыть кнопку:
self.ids.start_button.opacity = 0; self.ids.start_button.disabled = True; - Показать кнопку:
self.ids.start_button.opacity = 1; self.ids.start_button.disabled = False.
- Скрыть кнопку:
- Преимущество
match-caseпередif-else— улучшает читаемость при множественных вариантах таймера (pomodoro,break,break_long). Легко добавлять новые типы таймеров без вложенных условий; Clock.schedule_interval(self.update_time, 1)— планирует вызов методаupdate_timeкаждую секунду. Используется для обновления оставшегося времени таймера в реальном времени.
main.py
Обратно добавляю TimerScreen в виджеты главного приложения. Тут нет нового кода всё как и было раньше.
# Другой код
from kawai_focus.screens.timer_screen import TimerScreenclass KawaiFocusApp(MDApp, MenuApp):
"""Главный класс приложения"""
title = 'Kawai-Focus'
def build(self) -> MDScreenManager:
"""Создаёт и возвращает менеджер экранов приложения"""
self.theme_cls.theme_style = 'Dark'
# Загрузка kv файла
Builder.load_file('kv/timers_screen.kv')
Builder.load_file('kv/timer_screen.kv')
self.screen_manager = MDScreenManager()
self.screen_manager.add_widget(TimersScreen(name='timers_screen'))
self.screen_manager.add_widget(TimerScreen(name='timer_screen'))
return self.screen_manager
# Другой кодЗапуск с новым дизайном
Настало время посмотреть, как выглядит дизайн экрана «Таймер», а так же проверить работоспособность подключенных кнопок.
Запускаю приложение в terminal:
poetry run kawai-focusВыбираю «Таймер 1»:

Нажимаю на кнопку «Открыть»:

Открылся обновлённый экран «Таймер». Видно, что название и тип таймера теперь маленького компактного размера. Цифры таймера теперь гораздо больше и заметнее, а кнопки «Пауза» и «Стоп» теперь скрыты.
Нажимаю на кноку «Старт»:

Пошёл отсчёт времени и можно заметить, что кнопки «Пауза» и «Стоп» появились. Кнопка «Старт» исчезла с экрана. Всё как и задумывалось не нужные в логике кнопки пропадают, а нужные появляются.
Протестирую кнопку «Пауза»:

Теперь время остановилось, а кнопка «Пауза» пропала с экрана. Зато появилась кнопка «Старт», которой легко можно продолжить работу таймера.
Во время работы таймера можно заметить, что кнопки на боковом меню тусклые. Это значит, что они сейчас не активны и на них нельзя нажать. Это сделано для того, чтобы пользователь не отвлекался от работы на настройку таймера или случайно не вышел на другой экран из него.
Нажимаю на кнопку «Стоп»:

После нажатия на «Стоп» появился следующий вид таймера цепочки «Перерыв». Эта механика нужна для того чтобы пропустить часть таймера или таймер целиком либо перейти к следующему таймеру после сигнала.
Пока это всё по новому дизайну и функционалу таймера. Кнопка для экрана «Таймеры» на боковом меню ещё не подключена.
Анонс на следующие статьи
Сегодня я переписал дизайн экрана «Таймер» к Material Design виду. Так же логика работы таймера и кнопок стала более удобной. В целом мне нравится как преображается моё приложение в плане удобства и внешнего вида.
Моя следующая статья выйдет 04.12.2025. В ней я продолжу менять дизайн моего приложения. На этот раз я буду менять дизайн экрана «Конструктор таймера». Так же впереди подключение остальной логики приложения, например кнопки удаления таймера.
У меня есть хорошая новость =) Я отправил свой проект на конкурс от Gitverse Код без границ! Хоть MVP1 сейчас ещё не дописан до конца я уже борюсь за призовое место в номинации «Для всех и каждого». Так как моё приложение я пишу абсолютно для всех. Любой может использовать его для работы и хобби чтобы не выгорать. Пожелайте удачи =)
Если у вас есть мысли о том, как можно улучшить проект, пишите в комментариях — с удовольствием ознакомлюсь с вашими предложениями!
Читайте продолжение — не пропустите!
Заключение
- Обновлён дизайн экрана «Таймер»;
- Устранены проблемы с запуском backends связанных с библиотекой SDL2.
Ссылки к статье
- Мои статьи Arduinum628 на Код на салфетке;
- Репозиторий проекта на Github Kawai.Focus;
- Репозиторий проекта на Gitverse Kawai.Focus.
Комментарии
Оставить комментарийВойдите, чтобы оставить комментарий.
Комментариев пока нет.