mirror of
https://github.com/vide/matedroid.git
synced 2026-01-20 00:03:17 +08:00
754360c4a57d2c7ca854fcb9b2d01e7974c976ef
1 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f2654f1b97 |
feat: Stats for Nerds - comprehensive car statistics (#13)
* docs: add Stats for Nerds implementation plan Comprehensive plan for v0.8.0 feature that adds advanced statistics computed from local SQLite database: Architecture: - Two-tier data strategy: Quick Stats (from list APIs) vs Deep Stats (from detail APIs, background synced) - Room database with 5 tables (~10MB for heavy users) - WorkManager for background sync on app launch - Incremental sync with schema versioning for future-proofing Stats categories: - 🚗 Driving Records (distance, speed, elevation, efficiency) - ⚡ Charging Records (energy, cost, power, AC/DC ratio) - 🌡️ Temperature Records (hot/cold extremes) - 📅 Activity Stats (busiest day, averages) - 🔋 Energy Stats (consumption metrics) 7 implementation phases defined with detailed tasks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(stats): add Room database foundation for Stats for Nerds Phase 1 of Stats for Nerds feature - Database Foundation: Dependencies: - Add Room 2.6.1 for local SQLite database - Add WorkManager 2.9.1 for background sync - Add Hilt WorkManager integration Entities: - SyncState: Tracks sync progress per car with schema versioning - DriveSummary: Drive list data for Quick Stats (~300 bytes/record) - ChargeSummary: Charge list data for Quick Stats (~250 bytes/record) - DriveDetailAggregate: Computed aggregates for Deep Stats (~150 bytes/record) - ChargeDetailAggregate: Computed aggregates for Deep Stats (~120 bytes/record) DAOs with stat queries: - SyncStateDao: Sync progress tracking - DriveSummaryDao: Quick stats (total distance, max speed, busiest day, etc.) - ChargeSummaryDao: Quick stats (total energy, total cost, etc.) - AggregateDao: Deep stats (elevation, temperature, AC/DC ratio, etc.) Storage estimate: ~10 MB for heavy user (15k drives, 8k charges) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(stats): add background sync infrastructure Phase 2 of Stats for Nerds feature - Sync Infrastructure: Domain Models: - SyncProgress: Tracks sync phase and progress per car - OverallSyncStatus: Aggregates status across all cars Sync Components: - SyncManager: Single source of truth for sync state - Emits progress via StateFlow - Tracks summaries vs details sync separately - Supports schema versioning for future field additions - SyncRepository: Orchestrates data fetching and storage - syncSummaries(): Fast sync from list endpoints (Quick Stats) - syncDriveDetails(): Slow sync for elevation/temp extremes - syncChargeDetails(): Slow sync for AC/DC ratio, max power - Computes aggregates from detail positions/points - DataSyncWorker: WorkManager background worker - Runs on app launch - Syncs all cars in parallel - Network constraint required API Model Updates: - DrivePosition: Added climate_info for temperature tracking - DriveClimateInfo: inside_temp, outside_temp, is_climate_on App Initialization: - MateDroidApp: Custom WorkManager configuration with HiltWorkerFactory - AndroidManifest: Disabled default WorkManager initializer The sync now runs automatically when the app starts, populating the local database with drives/charges summaries and computing aggregates from detail data in the background. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(stats): add stats domain models and repository - Add CarStats domain model with QuickStats and DeepStats - Add YearFilter for filtering stats by year - Add record types (DriveElevationRecord, DriveTempRecord, etc.) - Implement StatsRepository with year-filtered queries - Support for checking sync progress and data availability Part of Stats for Nerds feature (Phase 3) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(stats): add Stats for Nerds screen and navigation - Create StatsViewModel with year filter support - Create StatsScreen with Quick Stats and Deep Stats cards - Add Trophy icon to CustomIcons - Add Screen.Stats route to NavGraph - Add onNavigateToStats callback to DashboardScreen Stats cards include: - Drives Overview (total, distance, energy, efficiency) - Charges Overview (total, energy added, cost) - Records (longest drive, top speed, most efficient, etc.) - Elevation stats (highest/lowest point, most climbing) - Temperature extremes (driving and cabin) - Charging power (max achieved) - AC/DC charging ratio Part of Stats for Nerds feature (Phase 4 & 5) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(dashboard): add stats entry point from car image - Make car image clickable to navigate to Stats screen - Add Analytics icon overlay on top-right of car image - Pass onNavigateToStats callback through Dashboard components - Add Screen.Stats route navigation from Dashboard Users can now tap the car image on the Dashboard to access the Stats for Nerds screen. Part of Stats for Nerds feature (Phase 6) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: add Stats for Nerds feature documentation - Add feature to CHANGELOG [Unreleased] section - Add feature to README features list 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(stats): resolve UI bugs in Stats for Nerds screen - Fix pull-to-refresh indicator not disappearing after refresh - Round all kWh totals to integers (Energy Used, Energy Added, Biggest Charge) - Make records individual clickable cards that navigate to drive/charge details - Add arrow indicator on navigable record cards 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(stats): improve Records section layout and content - Move Records section to the top of Stats screen - Show dates instead of addresses in record subtexts - Use standard KeyboardArrowRight icon for navigable cards - Move "Most Climbing" from Elevation card to Records section - Add "Most Expensive" charge record (by total cost) - Add "Priciest per kWh" charge record (by cost/kWh ratio) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(sync): run sync worker as foreground service The background sync was being killed by Android when the app went to background or screen turned off. This fix: - Adds FOREGROUND_SERVICE and FOREGROUND_SERVICE_DATA_SYNC permissions - Adds POST_NOTIFICATIONS permission for the sync notification - Runs DataSyncWorker as a foreground service with a notification - Creates a low-priority notification channel for sync status This ensures the sync continues even when the app is not in foreground. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(sync): use expedited work for Android 12+ compatibility Android 12+ blocks starting foreground services from background with: "startForegroundService() not allowed due to mAllowStartForeground false" This fix: - Uses setExpedited() on work request for priority execution - Adds getForegroundInfo() override for older API level compatibility - Falls back to regular work if quota is exhausted 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(stats): add debug sync log viewer In debug builds, tapping the sync progress card opens a dialog showing sync logs in real-time (like adb logcat). Includes: - SyncLogCollector singleton to capture log messages - Updated SyncRepository and DataSyncWorker to use log collector - Clickable SyncProgressCard in debug builds - Monospace scrollable log dialog with timestamps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(stats): add per-item progress logging for sync Logs now show each drive/charge as it's synced with remaining count: - "Drive 123 synced (45 remaining)" - "Charge 456 synced (12 remaining)" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(stats): improve records layout and climbing calculation UI changes: - Move stats icon to middle-right with > navigation arrow - Display records in 2-column grid for better use of space - Add temperature records (hottest/coldest drive/charge/cabin) - Add highest point as a record - Remove ElevationStatsCard (moved to records) - More compact record cards for grid layout Data changes: - Add startElevation and endElevation to DriveDetailAggregate - Calculate net climb as end - start altitude (not accumulated) - Update driveWithMostClimbing query to use net elevation - Bump SchemaVersion to 2 to trigger reprocessing - Add database migration from v1 to v2 Note: Charges sync after all drives - this is by design. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(sync): prevent aggregate deletion on summary refresh Root cause: @Insert(onConflict = REPLACE) performs DELETE + INSERT, which triggers CASCADE delete on aggregate tables, wiping all processed deep stats data. Fix: Use @Upsert instead of @Insert(REPLACE) for DriveSummaryDao and ChargeSummaryDao. @Upsert does a proper UPDATE when record exists, preserving foreign key relationships. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(sync): add automatic retry on network errors - Detect network errors (DNS, timeout, connection refused, etc.) - Return Result.retry() instead of failing silently - Add exponential backoff starting at 30 seconds - Log attempt number for debugging - Sync resumes from where it left off (not from zero) The sync is now resilient to temporary network issues and will automatically retry while preserving progress. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(sync): use REPLACE policy to unstick waiting work Changed ExistingWorkPolicy from KEEP to REPLACE so that opening the app always starts a fresh sync worker, replacing any stuck or long-waiting retry work. The sync still resumes from where it left off because processed aggregates are preserved in the database. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(stats): apply year filter to all records Records now properly filter by year when a year filter is selected: - Added range queries for fastest drive, most/least efficient drive - Added range queries for temperature records (hottest/coldest drive/charge) - Added range queries for elevation records (highest point, most climbing) - Added range query for max charging power record - StatsRepository now uses range queries in getDeepStatsForYear() - Made totalDrivingDays nullable in QuickStats for year view 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(stats): multiple fixes for stats screen - Fix sync progress stuck at 99%: check SyncPhase.COMPLETE to return 1.0 - Improve AC/DC detection: fallback to power-based detection (>22kW = DC) - Group record cards by theme (drives, elevation, temps, charges) - Each group starts on left column with proper alignment - Add Peak Power as a tappable charge record 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(sync): run as foreground service with persistent notification Call setForeground() at start of doWork() to: - Keep sync running when screen is off - Show persistent notification during sync - Update notification with sync progress 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(manifest): add foregroundServiceType for WorkManager service Declare SystemForegroundService with dataSync foregroundServiceType in manifest to allow foreground service for long-running sync. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(stats): use accumulated elevation gain for Most Climbing record The Most Climbing record was incorrectly using net climb (endElevation - startElevation) instead of accumulated elevation gain. This caused two problems: 1. Net climb doesn't capture drives that go up and down (e.g., +500m up, -400m down = +100m net vs +500m accumulated) 2. Many drives have null endElevation, causing them to be excluded from the query Fixed by: - Updated AggregateDao queries to ORDER BY elevationGain (accumulated) instead of (endElevation - startElevation) - Updated queries to filter by elevationGain IS NOT NULL instead of requiring both start/end elevations - Simplified StatsRepository to use elevationGain directly instead of recalculating net climb * fix(db): add migration to fix AC/DC detection for existing data Migration V2→V3 updates isFastCharger=1 for all charges where maxChargerPower > 22kW, ensuring DC charges are correctly detected even for data synced before the power-based detection was added. * feat(settings): add Force Full Resync button Adds a 'Force Full Resync' button in Settings under a new 'Data Management' section. The button: - Shows a confirmation dialog explaining what will happen - Resets sync progress for all cars - Triggers an immediate sync via WorkManager - Shows a snackbar confirmation when started This allows users to manually trigger a full re-download of all drive and charge details if stats seem incorrect. * fix(stats): use Teslamate's charger_phases logic for AC/DC detection Teslamate determines AC vs DC based on charger_phases: - DC: charger_phases is 0 or null (bypasses onboard charger) - AC: charger_phases is 1, 2, or 3 (uses onboard charger phases) Updated both: - SyncRepository: compute isFastCharger from mode of non-zero phases - Migration V2→V3: recalculate isFastCharger from stored chargerPhases This matches the exact logic Teslamate uses in its Grafana dashboards. * feat(charges): add AC/DC badges and improve charge detail charts - Add AC/DC badge (green/orange) to charge list items in Charges screen - Add AC/DC badge next to "Energy Added" in charge detail page - Hide voltage chart for DC charges (not meaningful for DC fast charging) - Add time labels (4 labels) to X axis of all charge detail graphs - Use Teslamate's charger_phases logic for DC detection (phases=0/null is DC) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(charges): fix AC/DC badge and preserve filter state on back navigation Fixes two bugs: 1. AC/DC badge was showing AC for all charges because it relied on pre-computed aggregates which may not exist yet. Now only shows badge when we have aggregate data for that specific charge. 2. Filter selection was reset when navigating back from charge details. Moved selectedFilter state from local remember to ViewModel so it persists across navigation. Changes: - Add getAllProcessedChargeIds() query to AggregateDao - Move DateFilter enum and selectedFilter state to ChargesViewModel - Only show AC/DC badge for charges that have been processed - ViewModel now initializes with default filter and preserves selection 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(charges): preserve scroll position on back navigation Save and restore the LazyColumn scroll position when navigating to and from charge details: - Add scrollPosition and scrollOffset to ChargesUiState - Add saveScrollPosition() to ChargesViewModel - Pass scroll state to ChargesContent and restore it via rememberLazyListState - Save current scroll position before navigating to charge detail 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(charges): restore AC/DC badges in charges list Reverted to always showing badges based on dcChargeIds. The previous approach of checking processedChargeIds caused badges to disappear when sync hadn't processed charge details yet. Badges will show DC if the charge is in dcChargeIds (sync has processed it as DC), otherwise AC. Once sync completes processing all charge details, all badges will be accurate. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(sync): handle Android 14+ foreground service restrictions On Android 14+, starting foreground services from background is restricted. The sync worker now gracefully handles setForeground() failures by: - Tracking if foreground service is available - Skipping subsequent setForeground() calls if the initial one failed - Logging the failure but continuing sync without the notification This fixes sync interruptions caused by ForegroundServiceStartNotAllowedException. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(stats): add "Most Distance Day" record Shows the day with the most total distance driven, displayed in the Records section of Stats for Nerds alongside the "Busiest Day" record. Added: - mostDistanceDay query to DriveSummaryDao (all-time and year range) - MostDistanceDayResult data class - mostDistanceDay field to QuickStats - Display in StatsScreen Records section with 🛣️ emoji 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(mileage): add day detail drill-down with drives list Adds a fourth level to the Mileage screen drill-down: - Year → Month → Day → Drive Detail Day Detail screen shows: - Summary card with total distance, avg distance, avg battery, avg energy - List of all drives for that day with start/end time, distance, duration, energy - Each drive links to the full Drive Detail screen Also: - Added arrow indicator (>) to day cards in month view - Made day cards clickable to navigate to day detail - Added selectedDay and selectedDayData to MileageUiState 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(stats): improve AC/DC ratio bar visualization - Change colors: green for AC, yellow/amber for DC - Make bar thicker (20dp height with rounded corners) - Calculate ratios based on charged kWh instead of charge count - Show energy values (kWh) in the stats row - Show charge counts in small text below each bar side - Remove percentage numbers from display Added new DAO queries to sum energy by AC/DC type: - sumAcChargeEnergy / sumAcChargeEnergyInRange - sumDcChargeEnergy / sumDcChargeEnergyInRange Added acChargeEnergyKwh and dcChargeEnergyKwh fields to DeepStats. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(stats): format energy as MWh when >= 1000 kWh - Show MWh with one decimal (e.g., "1.2 MWh") for values >= 1000 kWh - Show kWh without decimals (e.g., "850 kWh") for smaller values 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(stats): move AC/DC ratio near charges, remove power card - Moved AC/DC Charging Ratio card right after Charges Overview - Removed the Charging Power card (the "Peak Power" record is still in Records) - Simplified the deep stats section to only show temperature stats 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(stats): link day records to mileage day detail & format energy - Add navigation from "Most Distance Day" and "Busiest Day" records to their respective day detail views in Mileage screen - Add targetDay parameter to Mileage route for deep linking - Auto-navigate to specific day when targetDay is provided - Apply MWh formatting (>=1000 kWh) to Drives and Charges overview 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> |