Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 логировать только те действия, которые инициируются напрямую пользователем через меню (кормление, запуск таймера).
29 changes: 18 additions & 11 deletions src/core/input_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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}")
Expand Down Expand Up @@ -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
Expand Down
50 changes: 39 additions & 11 deletions src/ui/stats_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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':
Expand All @@ -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" <span style='color: #888;'>({current_val}/{goal})</span>"
progress_val = current_val
show_progress_bar = True

info_label = QLabel(f"<b>{ach_info['title']}</b>{progress_text}<br/><small>{ach_info['desc']}</small>")
info_label = QLabel(f"<b>{ach_info['title']}</b><br/><small>{ach_info['desc']}</small>")
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)
Expand Down
6 changes: 0 additions & 6 deletions src/ui/tray_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,17 +117,13 @@ 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", "Начата сессия работы")

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", "Начата сессия отдыха")
Expand All @@ -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
Expand Down
Binary file added verification/screenshots/stats_history.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added verification/screenshots/stats_progress.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.