diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..984aee8 --- /dev/null +++ b/SPEC.md @@ -0,0 +1,188 @@ +# Traccar Client Fork - Agent Specification + +This spec describes how to transform a fresh fork of [traccar/traccar-client](https://github.com/traccar/traccar-client) (Flutter) into a simplified, hardcoded tracking client. Apply all changes to the `main` branch. + +## Goal + +Create a minimal Traccar Client that: +- Hard-codes all server/tracking settings (no user configuration) +- Shows only a device ID and a tracking toggle in the UI +- Removes access to settings, status, manual send, QR scanning, and password protection + +## Upstream Repository + +``` +https://github.com/traccar/traccar-client +``` + +The app is a Flutter project targeting iOS and Android from a single Dart codebase. + +--- + +## 1. Configuration File: `lib/config.dart` + +Create a new file `lib/config.dart` with compile-time constants. This is the single source of truth for all hardcoded settings. A developer forking this project only needs to edit this file to reconfigure the app. + +```dart +class AppConfig { + AppConfig._(); + + /// Traccar server URL + static const String serverUrl = 'https://c.track.rs'; + + /// Location accuracy: 'highest', 'high', 'medium', or 'low' + static const String accuracy = 'highest'; + + /// Location update interval in seconds + static const int intervalSeconds = 60; + + /// Distance filter in meters (0 = disabled) + static const int distanceFilter = 0; + + /// Whether to enable stop detection + static const bool stopDetection = false; + + /// Whether to buffer locations when offline + static const bool buffer = true; + + /// Fastest location update interval in seconds (Android) + static const int fastestIntervalSeconds = 30; +} +``` + +All other modified files must read from `AppConfig` instead of using inline literals. + +--- + +## 2. Hardcoded Settings (via `AppConfig`) + +These values are set in `lib/config.dart` and enforced at runtime: + +| Setting | Constant | Default Value | +|--------------------|-----------------------------------|--------------------------| +| Server URL | `AppConfig.serverUrl` | `https://c.track.rs` | +| Location Accuracy | `AppConfig.accuracy` | `highest` | +| Interval | `AppConfig.intervalSeconds` | `60` | +| Distance Filter | `AppConfig.distanceFilter` | `0` (disabled) | +| Stop Detection | `AppConfig.stopDetection` | `false` (disabled) | +| Buffer | `AppConfig.buffer` | `true` | +| Fastest Interval | `AppConfig.fastestIntervalSeconds`| `30` | + +--- + +## 3. File Changes + +### 3.1 `lib/preferences.dart` + +Add `import 'config.dart';` at the top. + +#### `migrate()` method +After the existing migration logic and device ID generation, force-set values from `AppConfig`: + +```dart +// After device ID generation +await instance.setString(url, AppConfig.serverUrl); +await instance.setString(accuracy, AppConfig.accuracy); +await instance.setInt(interval, AppConfig.intervalSeconds); +await instance.setInt(distance, AppConfig.distanceFilter); +await instance.setBool(buffer, instance.getBool(buffer) ?? AppConfig.buffer); +await instance.setBool(stopDetection, AppConfig.stopDetection); +await instance.setInt(fastestInterval, instance.getInt(fastestInterval) ?? AppConfig.fastestIntervalSeconds); +``` + +#### `geolocationConfig()` method +Replace dynamic preference reads with `AppConfig` references: + +- `isHighestAccuracy` = `AppConfig.accuracy == 'highest'` +- `locationUpdateInterval` = `AppConfig.intervalSeconds * 1000` (but set to `0` in the config when highest accuracy) +- `desiredAccuracy`: Use `DESIRED_ACCURACY_NAVIGATION` on iOS, `DESIRED_ACCURACY_HIGH` on Android (when highest) +- `url`: `AppConfig.serverUrl` (passed through `_formatUrl`) +- `distanceFilter`: `AppConfig.distanceFilter.toDouble()` +- `locationUpdateInterval` (in config): `0` (when highest accuracy, for continuous tracking) +- `disableElasticity`: `true` +- `disableStopDetection`: `!AppConfig.stopDetection` +- `pausesLocationUpdatesAutomatically`: `false` on iOS, `null` on Android +- `fastestLocationUpdateInterval`: `0` when highest accuracy, else `AppConfig.fastestIntervalSeconds * 1000` + +### 3.2 `lib/main_screen.dart` + +**Complete rewrite.** Replace the entire file with a minimal screen containing: + +1. **AppBar** with title `'Traccar Client'` +2. **Centered body** with two cards: + - **Device ID card**: Shows label "Device ID" and a `SelectableText` displaying the device ID in monospace bold (`headlineSmall`) + - **Tracking toggle card**: A `SwitchListTile` labeled "Continuous Tracking" with subtitle showing state ("Sending location updates" / "Location tracking disabled") +3. **Tracking toggle behavior**: + - On: calls `bg.BackgroundGeolocation.start()` then checks battery optimizations + - Off: calls `bg.BackgroundGeolocation.stop()` + - Track `isMoving` state; set `activeTrackColor` to error color when `isMoving == false` (stationary warning) +4. **State listeners**: Listen to `onEnabledChange` and `onMotionChange` from BackgroundGeolocation +5. **Battery optimization dialog**: Keep the `_checkBatteryOptimizations` method that shows a dialog using `bg.DeviceSettings` + +**Remove entirely:** +- Settings button/navigation +- Status screen button/navigation +- "Send Location" button +- Password service import and usage +- Any reference to `SettingsScreen` or `StatusScreen` navigation + +**Imports needed:** `flutter/material.dart`, `flutter/services.dart`, `traccar_client/main.dart` (for `messengerKey`), `traccar_client/preferences.dart`, `flutter_background_geolocation`, `l10n/app_localizations.dart` + +### 3.3 `lib/settings_screen.dart` + +**Complete rewrite.** Replace with a minimal read-only screen (not navigable from main UI, but kept for completeness): + +Add `import 'config.dart';` at the top. + +- Device ID display (read-only `ListTile`) +- Continuous Tracking `SwitchListTile` (functional) +- Read-only disabled `ListTile` entries displaying values from `AppConfig`: + - Server: `AppConfig.serverUrl` + - Location Accuracy: `AppConfig.accuracy` + - Distance: `AppConfig.distanceFilter` (show "Disabled" when 0) + - Interval: `'${AppConfig.intervalSeconds} seconds'` + - Stop Detection: `AppConfig.stopDetection` (show "Disabled"/"Enabled") + +### 3.4 `ios/Runner.xcodeproj/project.pbxproj` + +Find the build phase named `FlutterFire: "flutterfire upload-crashlytics-symbols"` and prepend `exit 0` to the shell script to skip it. This prevents `flutterfire: command not found` build failures. + +The `shellScript` value should start with: +``` +\n#!/bin/bash\n# Skip Firebase Crashlytics for now\nexit 0\n +``` +followed by the original script content (which will never execute). + +--- + +## 4. Files NOT Modified + +These files remain unchanged from upstream: +- `lib/main.dart` - App entry point, Firebase init, app links, rate dialog +- `lib/geolocation_service.dart` +- `lib/configuration_service.dart` +- `lib/push_service.dart` +- `lib/quick_actions.dart` +- `lib/location_cache.dart` +- `lib/status_screen.dart` - Kept in codebase but not navigable +- `lib/password_service.dart` - Kept in codebase but unused +- `lib/qr_code_screen.dart` - Kept in codebase but not navigable +- `pubspec.yaml` +- All localization files (`lib/l10n/`) +- Android platform files + +--- + +## 5. Verification Checklist + +After applying changes: + +1. `flutter pub get` succeeds +2. `flutter build ios` succeeds (no flutterfire errors) +3. `flutter build apk --release` succeeds +4. App launches showing only device ID and tracking toggle +5. No settings, status, or send location buttons visible +6. Toggling tracking on starts background location reporting to `https://c.track.rs` +7. Device ID is auto-generated (8-digit random number) and persists across launches +8. Location updates use highest accuracy with 0 distance filter +9. Stop detection is disabled (tracking never auto-pauses)