import time from dataclasses import dataclass from datetime import datetime from typing import Optional @dataclass class StudySession: deck_name: str cards_studied: int duration_seconds: int session_end: datetime @property def description(self) -> str: return f"Anki: {self.deck_name} - {self.cards_studied} cards" @property def formatted_duration(self) -> str: mins, secs = divmod(self.duration_seconds, 60) if mins > 0: return f"{mins}m {secs}s" return f"{secs}s" @property def duration_parts(self) -> tuple[int, int, int]: hours = self.duration_seconds // 3600 minutes = (self.duration_seconds % 3600) // 60 seconds = self.duration_seconds % 60 return (hours, minutes, seconds) class SessionTracker: def __init__(self, min_cards: int = 5, min_duration: int = 30): self.min_cards = min_cards self.min_duration = min_duration self.reset() def reset(self): self.session_active = False self.session_start: Optional[float] = None self.cards_count = 0 self.deck_name: Optional[str] = None self.deck_id: Optional[int] = None def _start_new_session(self, deck_id: int, deck_name: str) -> None: self.session_active = True self.session_start = time.time() self.cards_count = 1 self.deck_name = deck_name self.deck_id = deck_id def on_card_shown(self, card): from aqt import mw current_deck = mw.col.decks.current() current_deck_id = current_deck['id'] current_deck_name = current_deck['name'] if not self.session_active: # Start new session self._start_new_session(current_deck_id, current_deck_name) elif self.deck_id != current_deck_id: # Deck changed - end current session and start new one if self.cards_count > 0: self.end_session() self._start_new_session(current_deck_id, current_deck_name) else: # Same deck, increment counter self.cards_count += 1 def on_state_change(self, new_state: str, old_state: str): if old_state == "review" and self.session_active: if self.cards_count > 0: self.end_session() def end_session(self): if not self.session_active or self.session_start is None: return duration = int(time.time() - self.session_start) # Too few cards - don't post if self.cards_count < self.min_cards: self.reset() return # Too short - don't post if duration < self.min_duration: self.reset() return session = StudySession( deck_name=self.deck_name, cards_studied=self.cards_count, duration_seconds=duration, session_end=datetime.utcnow() ) self.reset() # Imported here to avoid circular import from aqt import mw # Schedule on main thread to ensure UI updates properly mw.taskman.run_on_main( lambda: self._show_dialog(session) ) def _show_dialog(self, session: StudySession): # Imported here to avoid circular import from . import show_post_dialog show_post_dialog(session) def update_thresholds(self, min_cards: Optional[int] = None, min_duration: Optional[int] = None): if min_cards is not None: self.min_cards = min_cards if min_duration is not None: self.min_duration = min_duration