diff --git a/.jules/bolt.md b/.jules/bolt.md index c35890c..211b7ef 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -62,3 +62,11 @@ ## 2026-07-04 - [Activity History for User Engagement] **Инсайт:** Пользователи ценят возможность видеть историю своих действий (кормление, достижения). Разделение статистики на «Прогресс» и «Историю» через QTabWidget позволяет сохранить компактность окна, предоставляя при этом больше информации. **Действие:** Для логов и списков истории использовать QTabWidget с QListWidget, чтобы не перегружать основной экран статистики. + +## 2026-07-05 - [UI/UX Improvement: Achievement Progress Bars] +**Инсайт:** Текстовое отображение прогресса достижений (например, "5/10") менее информативно и визуально скучно по сравнению с графическими индикаторами. Использование компактных QProgressBar внутри списков достижений позволяет пользователю мгновенно оценить прогресс. +**Действие:** Всегда визуализировать численный прогресс через QProgressBar, если есть четкая цель (goal). + +## 2026-07-05 - [Code Quality: Redundant Event Logging] +**Инсайт:** Логирование событий одновременно в UI-слое (TrayMenu) и в слое логики (InputManager) приводит к дублированию записей в БД. Это замусоривает историю и может сбивать пользователя с толку. +**Действие:** Централизовать логирование ключевых событий (level_up, achievement) в InputManager. В TrayMenu логировать только те действия, которые инициируются напрямую пользователем через меню (кормление, запуск таймера). diff --git a/src/core/input_manager.py b/src/core/input_manager.py index 26ef6c9..8f4d435 100644 --- a/src/core/input_manager.py +++ b/src/core/input_manager.py @@ -60,6 +60,8 @@ def __init__(self, pet_window, data_store=None): self.last_mouse_pos = (0, 0) self.last_input_time = time.time() self.last_purr_time = 0 + self.last_pet_time = 0 + self.last_pet_mouse_pos = (0, 0) self.laser_mode = False self.monitor.key_pressed.connect(self.handle_key) @@ -165,15 +167,14 @@ def add_points(self, points): # Сохраняем в БД только когда накопилось 10 очков (примерно каждые 10 сек активной работы) if self.pending_points >= 10: - self.flush_points() + self.flush_all() # Проверка уровня (визуально можно чаще, используя буферизованные очки) virtual_total = self.last_affection_points + self.pending_points new_level = get_level(virtual_total) if new_level > old_level: - self.flush_points() # Обязательно сбрасываем перед уведомлением - self.db.log_event("level_up", f"Новый уровень: {new_level}") + self.flush_all() # Обязательно сбрасываем перед уведомлением self.window.show_message(f"Уровень дружбы повышен: {new_level} ❤️") self.window.sound_manager.play_sound("happy") self.db.log_event("level_up", f"Уровень повышен до {new_level}") @@ -334,14 +335,20 @@ def handle_mouse(self, x, y): # Оптимизация: сравнение квадрата расстояния (порог 60px -> 3600) if dist_sq_pet < 3600: - if self.window.animation_manager.current_state not in ["playing", "hunting", "shaking"]: - self.window.animation_manager.play_state("playing") - self.pending_stats["petting_count"] += 1 - if self.pending_stats["petting_count"] % 5 == 0: - self.check_for_achievements() - if now - self.last_purr_time > 2.0: - self.window.sound_manager.play_sound("purr") - self.last_purr_time = now + # Анти-фарм: требуем кулдаун 500мс и движение мыши хотя бы на 30пкс с прошлого поглаживания + m_dx = x - self.last_pet_mouse_pos[0] + m_dy = y - self.last_pet_mouse_pos[1] + if now - self.last_pet_time > 0.5 and (m_dx*m_dx + m_dy*m_dy) > 900: + if self.window.animation_manager.current_state not in ["playing", "hunting", "shaking"]: + self.window.animation_manager.play_state("playing") + self.pending_stats["petting_count"] += 1 + self.last_pet_time = now + self.last_pet_mouse_pos = (x, y) + if self.pending_stats["petting_count"] % 5 == 0: + self.check_for_achievements() + if now - self.last_purr_time > 2.0: + self.window.sound_manager.play_sound("purr") + self.last_purr_time = now def toggle_laser_mode(self): self.laser_mode = not self.laser_mode diff --git a/src/ui/stats_dialog.py b/src/ui/stats_dialog.py index afa5377..f287560 100644 --- a/src/ui/stats_dialog.py +++ b/src/ui/stats_dialog.py @@ -45,12 +45,14 @@ def setup_progress_tab(self, widget): # Общие очки и KPS в одной строке stats_row = QHBoxLayout() points_label = QLabel(f"Всего: {points} ❤️") + stats_row.addWidget(points_label) max_kps = self.db.get_stat("max_kps") - kps_label = QLabel(f"Рекорд скорости: {max_kps} кл/сек ⚡") - kps_label.setAlignment(Qt.AlignCenter) - kps_label.setStyleSheet("color: #555; font-size: 11px; margin-bottom: 5px;") - layout.addWidget(kps_label) + kps_label = QLabel(f"Рекорд: {max_kps} кл/сек ⚡") + kps_label.setAlignment(Qt.AlignRight) + kps_label.setStyleSheet("color: #555; font-size: 11px;") + stats_row.addWidget(kps_label) + layout.addLayout(stats_row) # Прогресс бар if points_for_next_level > 0: @@ -88,8 +90,14 @@ def setup_progress_tab(self, widget): ach_item_layout.addWidget(item_label) # Расчет прогресса - progress_text = "" - if not is_unlocked and 'goal' in ach_info and 'stat' in ach_info: + ach_text_layout = QVBoxLayout() + ach_text_layout.setSpacing(2) + + progress_val = 0 + goal = ach_info.get('goal', 0) + show_progress_bar = False + + if not is_unlocked and goal > 0 and 'stat' in ach_info: if self.parent() and hasattr(self.parent(), 'input_manager') and self.parent().input_manager: im = self.parent().input_manager if ach_info['stat'] == 'bonding_points': @@ -106,14 +114,34 @@ def setup_progress_tab(self, widget): if ach_info['stat'] == 'level': current_val, _, _, _ = get_level_info(points) - goal = ach_info['goal'] - if goal > 0: - progress_text = f" ({current_val}/{goal})" + progress_val = current_val + show_progress_bar = True - info_label = QLabel(f"{ach_info['title']}{progress_text}
{ach_info['desc']}") + info_label = QLabel(f"{ach_info['title']}
{ach_info['desc']}") if not is_unlocked: info_label.setStyleSheet("color: #888;") - ach_item_layout.addWidget(info_label) + ach_text_layout.addWidget(info_label) + + if show_progress_bar: + ach_progress = QProgressBar() + ach_progress.setMaximum(goal) + ach_progress.setValue(min(progress_val, goal)) + ach_progress.setFixedHeight(10) + ach_progress.setTextVisible(False) + ach_progress.setStyleSheet(""" + QProgressBar { + border: 1px solid #ddd; + border-radius: 5px; + background-color: #f0f0f0; + } + QProgressBar::chunk { + background-color: #4CAF50; + border-radius: 4px; + } + """) + ach_text_layout.addWidget(ach_progress) + + ach_item_layout.addLayout(ach_text_layout) ach_item_layout.addStretch() scroll_layout.addWidget(ach_widget) diff --git a/src/ui/tray_menu.py b/src/ui/tray_menu.py index 6ee6c32..55a1d1a 100644 --- a/src/ui/tray_menu.py +++ b/src/ui/tray_menu.py @@ -117,8 +117,6 @@ def show_message(self, title, message): def start_work_timer(self, checked=False): if self.window.timer_system: self.window.timer_system.start_pomodoro("work") - if self.window.input_manager and self.window.input_manager.db: - self.window.input_manager.db.log_event("pomodoro_start", "Начало работы") self.window.show_message("Пора работать! 🛠") if self.window.input_manager and self.window.input_manager.db: self.window.input_manager.db.log_event("pomodoro_start", "Начата сессия работы") @@ -126,8 +124,6 @@ def start_work_timer(self, checked=False): def start_break_timer(self, checked=False): if self.window.timer_system: self.window.timer_system.start_pomodoro("break") - if self.window.input_manager and self.window.input_manager.db: - self.window.input_manager.db.log_event("pomodoro_start", "Перерыв") self.window.show_message("Отдыхаем! ☕") if self.window.input_manager and self.window.input_manager.db: self.window.input_manager.db.log_event("pomodoro_start", "Начата сессия отдыха") @@ -152,8 +148,6 @@ def show_stats(self, checked=False): def feed_pet(self, checked=False): self.window.animation_manager.play_state("eating") if self.window.input_manager: - if self.window.input_manager.db: - self.window.input_manager.db.log_event("feeding", "Котик покормлен") self.window.input_manager.add_points(5) self.window.show_message("Мням! +5 ❤️") self.window.input_manager.pending_stats["total_feedings"] += 1 diff --git a/verification/screenshots/stats_history.png b/verification/screenshots/stats_history.png new file mode 100644 index 0000000..a212016 Binary files /dev/null and b/verification/screenshots/stats_history.png differ diff --git a/verification/screenshots/stats_progress.png b/verification/screenshots/stats_progress.png new file mode 100644 index 0000000..c87fd56 Binary files /dev/null and b/verification/screenshots/stats_progress.png differ