- Internal testing track: always uploads when secrets are configured
- Production track: only uploads when PLAY_STORE_PRODUCTION_ENABLED var is 'true'
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>
* 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>
* 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>
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>
* 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>
* feat(settings): add secondary server URL with fallback and reorganize settings UI
- Add optional secondary server URL for fallback when primary is unreachable
- Implement automatic fallback logic for network-level errors (timeout, connection refused, DNS failure)
- Reorganize Settings screen with collapsible sections:
- "Advanced network settings" (collapsed): secondary URL, API token, certificate toggle
- "Display Settings": currency, Teslamate base URL
- "Extra settings" (collapsed): show short drives toggle, force resync button
- Update TeslamateApiFactory to cache multiple API instances for both servers
- Update all tests for new settings structure
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(settings): test both primary and secondary servers in connection test
- Update TestResult to show individual results for each server
- Test Connection button now tests both servers when secondary is configured
- UI shows both server results with clear labels and status
- Add new tests for dual-server connection testing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: use dark blue color for map route path
Change the route path color from the theme primary color (grey) to a
dark blue color (0xFF2962FF) for better visibility on map tiles.
This addresses the feedback from Discussion #53 where the current
light grey route color was hard to see on the map.
The new color is similar to Tesla's navigator blue and provides much
better contrast against the OpenStreetMap tiles.
Closes: #53
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: use car palette accent color for map route path
Use the car's palette accent color for the route path instead of the
theme's primary color (grey). This ensures the route color matches
the car:
- Blue car → blue route
- Red car → red route
- White car → warm accent route
- etc.
The original code incorrectly used MaterialTheme.colorScheme.primary
(a generic grey) instead of the car-specific palette.accent color.
Addresses: Discussion #53
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* 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>
The Force Full Resync button was only resetting sync state flags without
actually deleting cached drives, charges, and aggregates. This caused
the sync to only retry unprocessed items rather than fetching everything
fresh from the API.
Changes:
- Add AggregateDao to SyncManager for clearing aggregates
- Add fullResetSync() method that deletes all cached data:
- drives_summary
- charges_summary
- drive_detail_aggregates
- charge_detail_aggregates
- Update forceResync() to call fullResetSync() instead of resetSync()
- Add warning icon and red "DELETE" text to confirmation dialog
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
TeslamateAPI returns pressure already in the user's preferred unit.
Remove incorrect bar→PSI conversion that caused 42 PSI to display as 610 PSI.
Fixes#46
- 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
- Move "Most Distance Day" record from Drives to Distances category
- Make gap records (Longest w/o Charging, Longest w/o Driving) tappable
- Add GapRecordDialog showing duration and start/end dates
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* 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>
* 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
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>
* 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>
* 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>
The distance filter (Commute/Day trip/Road trip) now affects all
components in the Drives view:
- Summary card shows totals for filtered distance category only
- Histogram charts display data for filtered distance category only
- Drive list continues to show filtered results
This provides a consistent view when analyzing specific trip types.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The AC/DC filter now affects all components in the Charges view:
- Summary card shows totals for filtered charge type only
- Histogram charts display data for filtered charge type only
- Charge list continues to show filtered results
This provides a consistent view when analyzing AC-only or DC-only
charging sessions.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* 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>
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>
Remove moshi-kotlin (reflection-based adapter) since the project uses
KSP code generation with @JsonClass(generateAdapter = true).
Benefits:
- Smaller APK size (no reflection library)
- Faster JSON parsing (generated code vs reflection)
- Cleaner dependency tree
Note: The Moshi kapt deprecation warning during hiltJavaCompileDebug
is a known Hilt bug and will persist until Hilt fixes it or Moshi 2.0
removes kapt support entirely.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- 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
The charges screen histograms now display total energy charged (kWh)
per day/week/month instead of the number of charging sessions.
This is more useful for users with smart charging systems (like Octopus)
that optimize costs by splitting charging into multiple partial sessions.
Fixes#18
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Per F-Droid reviewer feedback - use global Binaries field instead of
per-build binary field since the URL pattern is consistent.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>
Remove unnecessary submodules and sudo blocks - this is a standard
Gradle project that builds with default F-Droid infrastructure.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: add Makefile for easier debug builds
Add Makefile with targets for building, installing, and running debug
builds on connected devices. Update DEVELOPMENT.md to document the
available make targets.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add F-Droid and fastlane metadata for app store publishing
- Add fastlane metadata structure (title, descriptions, changelogs, screenshots, icon)
- Add F-Droid build recipe reference (com.matedroid.yml)
- Update CLAUDE.md with fastlane maintenance instructions
🤖 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>
Add Makefile with targets for building, installing, and running debug
builds on connected devices. Update DEVELOPMENT.md to document the
available make targets.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* 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
- 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>
* 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>