From 0a6814372d25a929533b37538368c1886b635c8d Mon Sep 17 00:00:00 2001 From: Arduinum628 Date: Thu, 2 Oct 2025 11:35:42 +0300 Subject: [PATCH] feat: added functional for new disign for timers_screen --- kawai_focus/database/cruds.py | 6 +- kawai_focus/main.py | 63 ++++++++++----- kawai_focus/menu_app.py | 115 +++++++++++++++++++++++++++ kawai_focus/screens/timers_screen.py | 52 ++++++++---- kv/timers_screen.kv | 111 ++++++++++++++++---------- 5 files changed, 267 insertions(+), 80 deletions(-) create mode 100644 kawai_focus/menu_app.py diff --git a/kawai_focus/database/cruds.py b/kawai_focus/database/cruds.py index d11fbab..62471c7 100644 --- a/kawai_focus/database/cruds.py +++ b/kawai_focus/database/cruds.py @@ -42,10 +42,10 @@ def list_timers() -> list[dict[str, int | str]]: return [ { 'timer_id': timer.id, - 'text': ( - f'{timer.title}\n' + 'title': timer.title, + 'info': ( f'Помидоров: {timer.count_pomodoro}, ' - f'размер помидора: {timer.pomodoro_time}' + f'Размер помидора: {timer.pomodoro_time}' ) } for timer in timers diff --git a/kawai_focus/main.py b/kawai_focus/main.py index 46ee47e..5f34c6d 100644 --- a/kawai_focus/main.py +++ b/kawai_focus/main.py @@ -1,42 +1,67 @@ import kivy kivy.require('2.3.1') -from kivy.app import App -from kivy.uix.screenmanager import ScreenManager from kivy.lang import Builder +from kivy.animation import Animation +from kivy.metrics import dp +from kivy.uix.behaviors import ButtonBehavior from kivy.logger import Logger import logging -from kawai_focus.screens.timer_screen import TimerScreen -from kawai_focus.screens.timer_constructor_screen import TimerConstructorScreen +from kivymd.app import MDApp +from kivymd.uix.screenmanager import MDScreenManager +from kivymd.uix.expansionpanel import MDExpansionPanel +from kivymd.uix.behaviors import RotateBehavior +from kivymd.uix.list import MDListItemTrailingIcon + +from kawai_focus.menu_app import MenuApp from kawai_focus.screens.timers_screen import TimersScreen -from kawai_focus.database.cruds import list_timers Logger.setLevel(logging.DEBUG) -class KawaiFocusApp(App): - """Класс для создания приложения""" +class TrailingPressedIconButton( + ButtonBehavior, RotateBehavior, MDListItemTrailingIcon +): + """Кнопка-иконка в конце элемента списка с поддержкой анимации""" + ... - title = 'Kawai.Focus' - def build(self): +class KawaiFocusApp(MDApp, MenuApp): + """Главный класс приложения""" + + def build(self) -> MDScreenManager: + """Создаёт и возвращает менеджер экранов приложения""" + + self.theme_cls.theme_style = 'Dark' # Загрузка kv файла - Builder.load_file('kv/timer_screen.kv') - Builder.load_file('kv/timer_constructor_screen.kv') Builder.load_file('kv/timers_screen.kv') - screen_manager = ScreenManager() - timers = list_timers() - timers_screen = TimersScreen(name='timers_screen') - timers_screen.ids.timers_view.data = timers + self.screen_manager = MDScreenManager() + self.timers_screen = TimersScreen(name='timers_screen') + self.screen_manager.add_widget(self.timers_screen) + + return self.screen_manager + + def tap_expansion_chevron( + self, panel: MDExpansionPanel, chevron: TrailingPressedIconButton + ) -> None: + """Анимация и переключение состояния раскрытия панели""" - screen_manager.add_widget(timers_screen) - screen_manager.add_widget(TimerConstructorScreen(name='timer_constructor_screen')) - screen_manager.add_widget(TimerScreen(name='timer_screen')) - return screen_manager + Animation( + padding=( + [0, dp(12), 0, dp(12)] if not panel.is_open else [0, 0, 0, 0] + ), + d=0.2, + ).start(panel) + panel.open() if not panel.is_open else panel.close() + ( + panel.set_chevron_down(chevron) + if not panel.is_open + else panel.set_chevron_up(chevron) + ) def main() -> None: diff --git a/kawai_focus/menu_app.py b/kawai_focus/menu_app.py new file mode 100644 index 0000000..de23f12 --- /dev/null +++ b/kawai_focus/menu_app.py @@ -0,0 +1,115 @@ +from kivy.utils import hex_colormap +from kivy.uix.widget import Widget +from materialyoucolor.utils.platform_utils import SCHEMES +from kivymd.uix.menu import MDDropdownMenu + + +class MenuApp: + """Класс для меню приложения""" + + menu: MDDropdownMenu = None + + def open_menu(self, menu_button) -> None: + """Открывает выпадающее меню""" + + menu_items = [] + + for item, method in { + 'Цвет темы': lambda: self.set_palette(), + 'Стиль темы': lambda: self.switch_theme(), + 'Тип схемы': lambda: self.set_scheme_type(), + 'Новый таймер': None + }.items(): + menu_items.append( + { + 'text': item, + 'on_release': method, + } + ) + + self.menu = MDDropdownMenu( + caller=menu_button, + items=menu_items, + ) + self.menu.open() + + def switch_palette(self, selected_palette: str) -> None: + """Переключает основной цвет темы""" + + self.theme_cls.primary_palette = selected_palette + + def switch_theme(self) -> None: + """Переключает светлую/тёмную тему""" + + self.theme_cls.switch_theme() + + def set_palette(self) -> None: + """Формирует и открывает меню выбора палитры""" + + instance_from_menu = self.get_instance_from_menu('Set palette') + available_palettes = [ + name_color.capitalize() for name_color in hex_colormap.keys() + ] + + menu_items = [] + for name_palette in available_palettes: + menu_items.append( + { + 'text': name_palette, + 'on_release': lambda x=name_palette: self.switch_palette(x), + } + ) + MDDropdownMenu( + caller=instance_from_menu, + items=menu_items, + ).open() + + def set_scheme_type(self) -> None: + """Формирует и открывает меню выбора типа цветовой схемы""" + + instance_from_menu = self.get_instance_from_menu("Switch scheme type") + + menu_items = [] + for scheme_name in SCHEMES.keys(): + menu_items.append( + { + 'text': scheme_name, + 'on_release': lambda x=scheme_name: self.update_scheme_name(x), + } + ) + MDDropdownMenu( + caller=instance_from_menu, + items=menu_items, + ).open() + + def update_scheme_name(self, scheme_name: str) -> None: + """Обновляет тип цветовой схемы""" + + self.theme_cls.dynamic_scheme_name = scheme_name + + def get_instance_from_menu(self, name_item: str) -> Widget: + """Возвращает экземпляр элемента меню по его названию""" + + index = 0 + rv = self.menu.ids.md_menu + opts = rv.layout_manager.view_opts + datas = rv.data[0] + + for data in rv.data: + if data.get('text') == name_item: + index = rv.data.index(data) + break + + instance = rv.view_adapter.get_view( + index, datas, opts[index].get('viewclass') + ) + return instance + + def disabled_widgets(self) -> None: + """Переключает активность виджетов (блокирует/разблокирует)""" + + for widget in self.root.ids.widget_box.children: + widget.disabled = not widget.disabled + + for widget in self.root.ids.custom_widget_box.children: + widget.disabled = not widget.disabled diff --git a/kawai_focus/screens/timers_screen.py b/kawai_focus/screens/timers_screen.py index 583b1c3..6c459bc 100644 --- a/kawai_focus/screens/timers_screen.py +++ b/kawai_focus/screens/timers_screen.py @@ -1,31 +1,55 @@ -from kivy.uix.screenmanager import Screen +from kivy.properties import StringProperty, NumericProperty +from kivymd.uix.screen import MDScreen +from kivymd.uix.expansionpanel import MDExpansionPanel from kawai_focus.utils.utils import calculate_time, gen_types_timers -from kawai_focus.database.cruds import get_timer +from kawai_focus.database.cruds import get_timer, list_timers +from kawai_focus.main import Logger -class TimersScreen(Screen): +class ExpansionPanelItem(MDExpansionPanel): + """Кастомный элемент панели с данными таймера""" + + timer_id = NumericProperty() + title = StringProperty() + info = StringProperty() + + +class TimersScreen(MDScreen): """Экран таймеры""" - def __init__(self, **kwargs): - super(TimersScreen, self).__init__(**kwargs) - - - def switch_new_timer(self, instance) -> None: - """Метод для переключения на экран Конструктор таймера""" + def on_enter(self, *args) -> None: + """Загружает список таймеров при входе на экран""" + + self.load_timers() - self.manager.current = 'timer_constructor_screen' + def load_timers(self) -> None: + """Загружает все таймеры и добавляет их в контейнер""" + self.ids.container.clear_widgets() + all_timers = list_timers() + + for timer_data in all_timers: + panel = ExpansionPanelItem( + timer_id=timer_data.get('timer_id'), + title=timer_data.get('title'), + info=timer_data.get('info'), + ) + self.ids.container.add_widget(panel) def switch_timer(self, instance) -> None: """Метод для кнопки переключения на выбранный таймер""" - + timer = get_timer(timer_id=instance.timer_id) + if not timer: + Logger.warning(f'Таймер с ID {instance.timer_id} не найден!') + return + screen_timer = self.manager.get_screen('timer_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 + time_calc = calculate_time(mm_user=timer.pomodoro_time) + screen_timer.timer_start_time = time_calc + screen_timer.ids.time_label.text = time_calc screen_timer.ids.title_label.text = timer.title screen_timer.source_timer_names = gen_types_timers(count_pomodoro=timer.count_pomodoro) diff --git a/kv/timers_screen.kv b/kv/timers_screen.kv index 2df9a4d..cfc5514 100644 --- a/kv/timers_screen.kv +++ b/kv/timers_screen.kv @@ -1,49 +1,72 @@ #:kivy 2.3.1 +# Кастомная панель для каждого таймера +: + MDExpansionPanelHeader: + MDListItem: + theme_bg_color: "Custom" + md_bg_color: self.theme_cls.surfaceContainerLowColor + ripple_effect: False + + MDListItemSupportingText: + text: root.title + + TrailingPressedIconButton: + id: chevron + icon: "chevron-right" + on_release: app.tap_expansion_chevron(root, chevron) + + MDExpansionPanelContent: + orientation: "vertical" + padding: "12dp", 0, "12dp", "12dp" + md_bg_color: self.theme_cls.surfaceContainerLowColor + adaptive_height: True + + MDLabel: + text: "Информация о таймере" + adaptive_height: True + padding_x: "16dp" + padding_y: "12dp" + + MDListItem: + theme_bg_color: "Custom" + md_bg_color: self.theme_cls.surfaceContainerLowColor + ripple_effect: False + on_release: None + MDListItemLeadingIcon: + icon: "information-outline" + MDListItemHeadlineText: + text: root.info + + MDBoxLayout: + padding: [8, 8, 0, 0] + size_hint_y: None + height: self.minimum_height + + MDButton: + style: "outlined" + size_hint_x: 1 + + MDButtonText: + text: "Открыть" + +# Основной экран : - FloatLayout: - RecycleView: - id: timers_view - size_hint: 1, 1 - pos_hint: {"x": 0, "y": 0} + md_bg_color: self.theme_cls.backgroundColor + + MDIconButton: + on_release: app.open_menu(self) + pos_hint: {"top": .98} + x: "12dp" + icon: "menu" - viewclass: "TimerButton" + ScrollView: + size_hint_x: .9 + pos_hint: {"center_x": .5, "center_y": .5} + do_scroll_x: False + do_scroll_y: True - RecycleBoxLayout: - default_size: None, dp(50) - default_size_hint: 1, None - size_hint_y: None - height: self.minimum_height - orientation: "vertical" - spacing: dp(5) - - Button: - id: new_timer - text: 'Новый таймер' - size_hint: None, None - height: 40 - width: 120 - pos_hint: {"center_x": 0.3, "center_y": 0.1} - on_release: root.switch_new_timer(self) - -: - timer_id: None - text: "" - on_release: app.root.get_screen("timers_screen").switch_timer(self) - size_hint: None, None - height: dp(50) - background_normal: "" # отключаем стандартный фон - background_color: 0, 0, 0, 0 # прозрачный - - canvas.before: - Color: - rgba: 1, 1, 1, 1 # белый контур - Line: - width: 1.5 - rectangle: (self.x, self.y, self.width, self.height) - - Color: - rgba: (0, 0.5, 1, 0.5) if self.state == "down" else (0, 0, 0, 0) - Rectangle: - pos: self.pos - size: self.size + MDList: + id: container + spacing: "8dp" + padding: [56, 8, 56, 0] # [слева, сверху, справа, снизу] \ No newline at end of file