Commit Graph

60 Commits

Author SHA1 Message Date
Davide Ferrari
ebb93aa700 chore: release v0.11.1 2026-01-19 11:28:02 +01:00
Davide Ferrari
75d14500b8 chore: release v0.11.0 2026-01-19 10:44:29 +01:00
Davide Ferrari
e78cc5e582 fix: hide charger section and charts for DC charging sessions (#73)
Fixes #65

Contributed by @MARMdeveloper - this PR applies their changes from #66 with conflicts resolved.
2026-01-19 10:09:12 +01:00
Davide Ferrari
8c60ded4dc chore: release v0.10.0
- Fullscreen mode for line charts
- Weather Along the Way in Drive details
- Optimized chart rendering with LTTB downsampling
- Improved chart labels (time and value axes)
- Filter and scroll state persistence fixes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 11:17:02 +01:00
Davide Ferrari
025f4dae4d feat: add fullscreen mode for line charts in Drive and Charge details (#61)
* feat: add fullscreen mode for line charts in Drive and Charge details

- Add FullscreenLineChart component with fullscreen icon in lower-right corner
- Tap icon to expand chart to fullscreen in landscape orientation
- Add back arrow button in top-left corner to exit fullscreen
- Chart automatically scales to fill available screen space
- Lock screen to landscape mode while in fullscreen

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: improve fullscreen chart experience

- Hide system bars (status bar, navigation) for better fullscreen
- Force dialog window to MATCH_PARENT dimensions
- Restore portrait mode and system bars when exiting fullscreen

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 10:50:51 +01:00
Davide Ferrari
6f09e1b00a docs: fix changelog links for v0.9.4
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 11:02:24 +01:00
Davide Ferrari
2a50ea7f94 perf(charts): optimize line charts with LTTB downsampling for smooth scrolling (#59)
* perf(charts): optimize line charts with LTTB downsampling for smooth scrolling

Long trips (>100km) and charging sessions could have hundreds or thousands
of data points, causing scroll stutter when rendering charts.

Changes:
- Add OptimizedLineChart component with LTTB (Largest Triangle Three Buckets)
  downsampling algorithm
- Reduce data points to max 150 while preserving visual shape
- Use Path-based drawing instead of individual line segments
- Cache computed values with remember() to prevent recalculation during scroll
- Support tap-to-show-tooltip functionality
- Support time labels on X-axis for charge detail charts

Both DriveDetailScreen and ChargeDetailScreen now use the optimized component.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(charts): add proper axis labels following chart guidelines

Apply chart guidelines from CLAUDE.md:

Y-axis labels:
- Show 4 labels at: 1st quarter (25%), half (50%), 3rd quarter (75%), end (100%)
- Skip the minimum (0%) label at top

X-axis time labels:
- Show 5 labels at: start (0%), 1st quarter (25%), half (50%), 3rd quarter (75%), end (100%)
- Added time label extraction for DriveDetailScreen charts
- Updated ChargeDetailScreen to use 5 labels instead of 4

Both screens now follow the standardized chart guidelines for consistent UX.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 11:01:52 +01:00
Davide Ferrari
2a6c5a6c78 fix(drives): preserve filters and scroll position on navigation (#58)
Fixes issue where date/distance filters and scroll position were reset
when navigating back from drive details:

- Move date filter state from composable to ViewModel
- Add scroll position tracking in ViewModel
- Only apply default 7-day filter on first initialization
- Restore scroll position when returning to drives list

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 09:50:17 +01:00
Davide Ferrari
83ab18ea3f feat(drives): add Weather Along the Way to drive details (#57)
* feat(drives): add Weather Along the Way to drive details

Shows historical weather conditions along the drive route using the
Open-Meteo API. Weather point frequency adapts to drive length:
- Under 10 km: destination only
- Under 30 km: start and end
- Under 150 km: every 25 km
- Over 150 km: every 35 km

Displays time, distance, weather icon, and temperature in a table.
Weather conditions: Clear, Partly Cloudy, Fog, Drizzle, Rain, Snow,
Thunderstorm.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(weather): show "End" label for last weather point

- Last weather point now displays "End" instead of distance
- Intermediate points show distance in km or miles based on user setting
- First point shows "Start" as before

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(weather): move icon to right-most position in weather column

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 09:32:18 +01:00
Davide Ferrari
b1e59baf0c fix(stats): show driving days count when filtering by year (#55)
* build: add make targets for release APK build and install

* fix(stats): show driving days count when filtering by year

The "Driving Days" stat in Stats for Nerds was showing "Null" when
filtering by a specific year because the value was explicitly set to
null in the repository.

Changes:
- Add countDrivingDaysInRange() query to DriveSummaryDao
- Use it in StatsRepository for year-filtered stats
- Add null-safe display in StatsScreen UI (defensive)

Fixes #52

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: lower minSdk to API 28 (Android 9)

The minSdk was unnecessarily bumped to 29 for SQLite window functions,
but those were never actually used - the driving streak calculation
is done in Kotlin instead.

All SQL queries use only julianday() and DATE() functions which are
available on all SQLite versions. The foreground service code already
has proper fallbacks for pre-Android 10 devices.

This restores support for Android 9 devices.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: release v0.9.4

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 09:31:46 +01:00
Davide Ferrari
d748c50f12 chore: release v0.9.3
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 08:28:30 +01:00
Davide Ferrari
baa4875d59 chore: release v0.9.2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 07:32:59 +01:00
Davide Ferrari
70ae969d0c fix(stats,dashboard): scale UI with system font size (#48)
- Stats: Record cards height now scales with fontScale to prevent vertical text clipping
- Stats: Added TextOverflow.Ellipsis for horizontal overflow fallback
- Dashboard: Elevation label no longer wraps unit to next line

Fixes #47
2026-01-12 21:48:13 +01:00
Davide Ferrari
8d3eaba273 chore: release v0.9.0
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 21:50:01 +01:00
Davide Ferrari
a80433d00d feat(stats): add new records and categorized swipeable UI (#44)
* feat(stats): add new records and categorized swipeable UI

- Add new records: longest gap without charging, longest gap without
  driving, longest driving streak, biggest battery gain, biggest
  battery drain
- Organize records into 4 swipeable categories: Drives, Battery,
  Weather & Altitude, Distances
- Add horizontal pager with emoji+label indicators
- Compute driving streak in Kotlin (Room doesn't support window
  functions in CTEs)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(stats): fixed page height with 6 records per page constraint

- Each page now displays exactly 6 record slots (3 rows × 2 columns)
- Categories with more than 6 records are split into multiple pages
- Added page indicator dots for multi-page categories
- Added RECORDS_PER_PAGE constant with comment documenting the hard
  constraint for future updates

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(stats): enforce fixed row height for consistent page size

- Set RECORD_CARD_HEIGHT = 72.dp for each row
- Each row now has fixed height regardless of content
- Empty placeholder boxes fill the same space as cards
- Prevents page resizing when swiping between categories

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(stats): remove redundant category title from record pages

The swipe indicator already shows current category, no need for title

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: update changelog with swipeable record categories

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 21:38:33 +01:00
Davide Ferrari
5a2e3df4ae feat(stats): add "Longest Range" record (max distance between charges) (#41)
* chore: bump minSdk from API 26 to API 29

This enables SQLite window functions (LAG, LEAD, etc.) for efficient
record calculations without requiring sync pre-computation.

Android 10 (API 29) was released in September 2019 and covers 95%+
of active Android devices as of 2026.

* feat(stats): add SQL query for max distance between charges

Uses SQLite LAG window function to efficiently find the maximum
distance traveled between consecutive charges. Includes both
all-time and date-range variants for year filtering support.

* feat(stats): add MaxDistanceBetweenChargesRecord domain model

Add new record type to QuickStats for tracking the maximum distance
driven between two consecutive charging sessions.

* feat(stats): integrate max distance between charges in repository

Wire up the new DAO query to QuickStats for both all-time and
year-filtered views. No sync required - data is computed instantly
from the existing charges summary table.

* feat(stats): display max distance between charges in Records UI

Add 'Longest Range' record showing the maximum distance traveled
between two consecutive charges. Displays with battery emoji and
date range, tapping navigates to the ending charge detail.

* feat(stats): add SQL query for max distance between charges

Uses a self-join with correlated subquery to efficiently find the
maximum distance traveled between consecutive charges. Includes both
all-time and date-range variants for year filtering support.

* fix(stats): use sum of drives instead of odometer diff for longest range

The odometer difference between charges can include unlogged drives
(e.g., when TeslaMate was down). Now we sum actual logged drives
between charges, which gives accurate results even with data gaps.

* feat(stats): add popup dialog for longest range record details

When tapping the 'Longest Range' record, show a dialog with:
- Total distance and date range summary
- Scrollable list of all drives that make up the record
- Each drive is tappable to navigate to drive details

This provides full context about what drove the record instead
of just navigating to a single charge.

* docs: simplify changelog entries for users
2026-01-11 18:37:46 +01:00
Davide Ferrari
c6b1fec3e3 fix(stats): use configured currency instead of hardcoded EUR (#37)
Stats for Nerds now displays costs in the user's configured currency
instead of always showing EUR. This affects:
- Total Cost in Charges Overview
- Avg Cost/kWh in Charges Overview
- Most Expensive charge record
- Priciest/kWh charge record

Cherry-picked from hotfix/0.8.3 (v0.8.3)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 17:45:15 +01:00
Davide Ferrari
2ba225873c feat(dashboard): add charging glow effect around car image (#39)
* feat(dashboard): add charging glow effect around car image

When the car is charging, a glowing effect now appears around the car
image on the dashboard. The glow:
- Follows the exact shape of the car (extracted from PNG alpha channel)
- Uses the palette accent color matching the car's exterior
- Has a 70px blur radius for a soft, attractive effect
- Only appears when actively charging

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(dashboard): add breathing glow animation when charging

The car image now displays a breathing glow effect when the vehicle
is charging:
- Glow pulses smoothly in opacity (30% to 90%) with sine easing
- Color shifts subtly from accent color toward AC/DC charging color
- AC charging: shifts toward green (StatusSuccess)
- DC charging: shifts toward orange (StatusWarning)
- 2 second animation cycle
- Glow follows the exact shape of the car using alpha channel extraction

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: add breathing glow effect to changelog

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 17:31:11 +01:00
Davide Ferrari
de068b90c4 feat(drives): add top speed histogram chart (#35)
* feat(drives): add top speed histogram chart

Add a fourth swipeable chart to the Drives view showing maximum speed
per day/week/month. The speed is displayed in km/h or mph depending
on the user's measurement system preference.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: update changelog with filter and chart improvements

- Charges AC/DC filter now applies to summary card and charts
- Drives distance filter now applies to summary card and charts
- Drives now has a fourth chart showing Top Speed per period

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 10:25:12 +01:00
Davide Ferrari
9c5cb99636 feat(charges,drives): add swipeable charts for multiple metrics (#32)
* feat(charges): add swipeable charts for energy, cost, and count

Add HorizontalPager with three swipeable chart views:
- Energy per Day/Week/Month (existing)
- Cost per Day/Week/Month (new)
- Number of Charges per Day/Week/Month (new)

Charts are grouped by the same time granularity based on the selected
date filter. Page indicator dots below the chart show current position.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(drives): add swipeable charts for count, time, and distance

Add HorizontalPager with three swipeable chart views:
- Number of Drives per Day/Week/Month (existing)
- Time Spent Driving per Day/Week/Month (new)
- Distance Travelled per Day/Week/Month (new)

Charts are grouped by the same time granularity based on the selected
date filter. Distance chart respects metric/imperial unit setting.
Page indicator dots below the chart show current position.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(drives): rename time chart to 'Driving time per...'

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 23:48:03 +01:00
Davide Ferrari
013f92eff3 feat(charges): add AC/DC filter to charges list (#22) (#31)
Add filter chips to show only AC or DC charging sessions:
- ChargeTypeFilter enum with ALL, AC, DC options
- Toggle behavior: tap selected filter again to reset to ALL
- Filter chips use matching colors (green for AC, orange for DC)
- Filtering logic based on dcChargeIds from local database

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 23:12:27 +01:00
Davide Ferrari
2229cbb26a feat: add charging power gauge with AC/DC badge on dashboard (#28)
- Add circular gauge showing charging rate relative to max capacity
- AC (green): gauge fills based on current vs max requested amps
- DC (yellow): gauge fills based on power vs max (250 kW NMC, 170 kW LFP)
- Show AC charging details below SoC bar (Voltage, Current, Phases)
- Add battery chemistry detection (LFP vs NMC) based on trim_badging

Closes #27
2026-01-10 13:32:46 +01:00
Davide Ferrari
f9c1f5dc47 chore: release v0.8.2 - disable DependencyInfoBlock for F-Droid
- Add dependenciesInfo block to disable Google's encrypted dependency metadata
- Bump version to 0.8.2 (versionCode 13)
- Update fastlane changelog and F-Droid metadata

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 15:44:56 +01:00
Davide Ferrari
1c9bd8c504 chore: release v0.8.1 2026-01-06 21:25:55 +01:00
Davide Ferrari
f738c840af refactor(dashboard): remove chart icon from stats button overlay (#21)
* refactor(dashboard): remove chart icon from stats button overlay

Keep only the arrow indicator for the stats navigation button
on the car image, making the UI cleaner and less cluttered.

* docs: add changelog entry for stats button simplification
2026-01-06 21:25:18 +01:00
Davide Ferrari
3f0fc1da61 feat(sync): add periodic sync and pull-to-refresh sync in Stats screen (#20)
- Pull-to-refresh now triggers a background sync to fetch new data
- Automatic sync every 60 seconds while Stats screen is visible
- Uses ExistingWorkPolicy.KEEP to avoid interrupting running syncs
- Skips sync if already syncing (via SyncManager.syncStatus check)

This ensures new drives/charges are synced while the app is running,
without requiring the user to close and reopen the app.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 21:17:51 +01:00
Davide Ferrari
5f65c84282 fix(sync): trigger initial sync automatically on first-time setup (#19)
* fix(sync): trigger initial sync automatically on first-time setup

On first run of 0.8.0, the initial sync was not triggered because:

1. DataSyncWorker ran immediately on app start, but server wasn't
   configured yet, so getCars() returned "Server not configured"
2. This error caused Result.failure() - permanently failing the work
3. After user saved settings, no sync was triggered

Fixed by:
- DataSyncWorker now returns Result.success() when server is not
  configured (nothing to do yet, not an error)
- SettingsViewModel.saveSettings() now triggers sync after saving,
  ensuring sync starts as soon as server is configured

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(stats): show sync progress in Stats screen during initial sync

The Stats screen was showing "No data available" without any progress
indicator during initial sync. Fixed by:

- StatsViewModel now observes SyncManager.syncStatus and reloads stats
  whenever sync progress changes
- Empty state shows spinning indicator + sync phase + progress bar
  even when no data is available yet (during summaries sync)
- UI updates reactively as new data is synced

This ensures users see sync progress feedback immediately when
opening Stats screen during initial sync.

🤖 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>
2026-01-06 21:13:06 +01:00
Davide Ferrari
9a39351be1 chore: release v0.8.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 19:32:53 +01:00
Davide Ferrari
e3dfe15b81 fix(mileage): calculate avg/year using actual elapsed time since first drive (#16)
Previously, the Avg/Year calculation divided total distance by the number
of distinct calendar years (e.g., 2023, 2024, 2025, 2026 = 4 years), which
gave incorrect results for users who started driving mid-year.

Now calculates: (total distance / days since first drive) × 365

Also adds an info icon (i) next to Avg/Year that shows a dialog explaining
the calculation method and the first drive date.

Fixes #10

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 19:23:12 +01:00
Davide Ferrari
7f6218e5c0 feat(assets): add Model Y Juniper Performance and Premium support (#15)
- Add 6 Performance images with 21" Überturbine wheels
- Add 15 Standard/Premium images with 18"/19"/20" wheels
- Support 6 colors for Premium: PPSW, PN01, PX02, PN00, PR01, PPSB
- Detect Performance via trim_badging (P74D) or 21" wheels
- Detect Premium via 19" Crossflow or 20" Helix wheels

Fixes #1
2026-01-05 19:08:30 +01:00
Davide Ferrari
a44f808861 feat(charges): add tappable cost card to edit in Teslamate (#14)
* feat(charges): add tappable cost card to edit in Teslamate

- Add Teslamate Base URL setting in Settings (above Display Settings)
- Make "Connect to TeslamateApi" header smaller (titleMedium)
- Cost card in Charges list shows external link icon when cost is 0
- Tapping opens browser to Teslamate charge-cost endpoint

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(charges): show edit icon on all costs, not just zero

* docs: update CHANGELOG with charge cost edit feature

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 18:17:07 +01:00
Davide Ferrari
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>
2026-01-05 17:52:09 +01:00
Davide Ferrari
8383047d50 chore: release v0.7.1
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-25 19:36:41 +01:00
Davide Ferrari
f619c5308c fix(dashboard): use API counts for drives/charges totals (#9)
Use total_charges and total_drives fields from the teslamate_stats object
in the /api/v1/cars endpoint instead of fetching and counting all
individual drives/charges. This is more efficient and avoids potential
race conditions with large datasets.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-25 19:35:59 +01:00
Davide Ferrari
39acebe69a chore: release v0.7.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 16:14:01 +01:00
Davide Ferrari
31103a60e3 feat(drives): add distance filter for drives (#8)
Add a new filter row in Drives screen to filter by drive length:
- Commute (< 10 km / 6 mi)
- Day trip (10-100 km / 6-60 mi)
- Road trip (> 100 km / 60 mi)

Filter labels automatically adapt to user's metric/imperial unit setting.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 16:13:16 +01:00
Davide Ferrari
52b8162b13 feat: add navigation arrow hints to mileage cards (#7)
- Add right arrow icon to year cards indicating they navigate to monthly details
- Add right arrow icon to month cards indicating they navigate to daily details
- Round mileage values in year and month cards to whole numbers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 15:58:33 +01:00
Davide Ferrari
e9c6383062 Merge pull request #5 from vide/fix/dashboard-counts-race-condition
fix: resolve race condition in dashboard counts display
2025-12-24 15:56:46 +01:00
Davide Ferrari
71ea9ce911 fix: round mileage totals to whole numbers (#6)
Remove unnecessary decimal precision from Total and Avg/Year mileage
values in the Mileage screen summary row.

Closes #4

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 15:51:50 +01:00
Davide Ferrari
80a86fa373 fix: resolve race condition in dashboard counts display
The DashboardViewModel was using non-atomic state updates
(_uiState.value = _uiState.value.copy(...)) which caused a race
condition when two coroutines tried to update state concurrently.

When loading charges and drives counts in parallel:
1. Both coroutines read the state snapshot
2. Both do async work (API calls)
3. Each writes back with .copy() - second write overwrites first

With large datasets, API calls take longer, making this race window
much larger - explaining why users with many drives/charges saw
missing totals on the dashboard.

Fixed by using MutableStateFlow.update{} throughout the ViewModel,
which provides atomic state updates. Also updated tests to match
the current ViewModel API (CarStatusWithUnits, GeocodingRepository).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 15:38:02 +01:00
Davide Ferrari
9c77434825 feat: add release notes link to software version cards
Each software version card now shows an external link icon next to the
version number. Tapping it opens the NotATeslaApp release notes page
for that specific version.

URL pattern: https://www.notateslaapp.com/software-updates/version/{version}/release-notes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 19:36:41 +01:00
Davide Ferrari
89b60a623b fix: show all software updates instead of only 100 (#2)
The TeslaMateAPI updates endpoint supports pagination with `page` and
`show` query parameters, defaulting to show=100 results.

The app was not passing these parameters, causing users with more than
100 software updates to only see the most recent 100.

Fixed by adding pagination parameters to the API interface and passing
show=50000 in the repository (same pattern used for charges and drives).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 19:05:07 +01:00
Davide Ferrari
91f017cf1e chore: release v0.6.1
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-22 17:58:27 +01:00
Davide Ferrari
63ca2295b0 chore: release v0.6.0
- Bump version to 0.6.0 (versionCode 7)
- Update repository URLs to github.com/vide/matedroid
- First public GitHub release with automated APK builds

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-22 17:10:37 +01:00
Davide Ferrari
5da3fcc4c5 feat: show version number at bottom of Settings screen 2025-12-22 14:41:30 +01:00
Davide Ferrari
4c9751b329 chore: release v0.5.0 2025-12-22 14:36:48 +01:00
Davide Ferrari
edb1dac9a4 feat: add GitHub Actions workflow for release builds
- Build APK automatically when a release is published
- Upload APK as release asset
- Support custom keystore signing via GitHub secrets
- Fall back to debug signing when secrets not configured
2025-12-22 11:52:28 +01:00
Davide Ferrari
cc5222c216 fix: allow unsecure HTTP connections to TeslamateApi
Changed android:usesCleartextTraffic from false to true in the
AndroidManifest to allow HTTP (non-HTTPS) connections to Teslamate
servers running on local networks without TLS.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-22 10:06:14 +01:00
Davide Ferrari
82b1addc7b docs: add multi-car support to changelog
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 23:57:48 +01:00
Davide Ferrari
1d857030cc docs: simplify changelog, focus on user-visible features
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 23:44:17 +01:00