diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 4533c73..d8c551a 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -344,7 +344,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\n#!/bin/bash\nPATH=\"${PATH}:$FLUTTER_ROOT/bin:${PUB_CACHE}/bin:$HOME/.pub-cache/bin\"\n\nif [ -z \"$PODS_ROOT\" ] || [ ! -d \"$PODS_ROOT/FirebaseCrashlytics\" ]; then\n # Cannot use \"BUILD_DIR%/Build/*\" as per Firebase documentation, it points to \"flutter-project/build/ios/*\" path which doesn't have run script\n DERIVED_DATA_PATH=$(echo \"$BUILD_ROOT\" | sed -E 's|(.*DerivedData/[^/]+).*|\\1|')\n PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"${DERIVED_DATA_PATH}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\"\nelse\n PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"$PODS_ROOT/FirebaseCrashlytics/run\"\nfi\n\n# Command to upload symbols script used to upload symbols to Firebase server\nflutterfire upload-crashlytics-symbols --upload-symbols-script-path=\"$PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT\" --platform=ios --apple-project-path=\"${SRCROOT}\" --env-platform-name=\"${PLATFORM_NAME}\" --env-configuration=\"${CONFIGURATION}\" --env-project-dir=\"${PROJECT_DIR}\" --env-built-products-dir=\"${BUILT_PRODUCTS_DIR}\" --env-dwarf-dsym-folder-path=\"${DWARF_DSYM_FOLDER_PATH}\" --env-dwarf-dsym-file-name=\"${DWARF_DSYM_FILE_NAME}\" --env-infoplist-path=\"${INFOPLIST_PATH}\" --default-config=default\n"; + shellScript = "\n#!/bin/bash\n# Skip Firebase Crashlytics for now\nexit 0\nPATH=\"${PATH}:$FLUTTER_ROOT/bin:${PUB_CACHE}/bin:$HOME/.pub-cache/bin\"\n\nif [ -z \"$PODS_ROOT\" ] || [ ! -d \"$PODS_ROOT/FirebaseCrashlytics\" ]; then\n # Cannot use \"BUILD_DIR%/Build/*\" as per Firebase documentation, it points to \"flutter-project/build/ios/*\" path which doesn't have run script\n DERIVED_DATA_PATH=$(echo \"$BUILD_ROOT\" | sed -E 's|(.*DerivedData/[^/]+).*|\\1|')\n PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"${DERIVED_DATA_PATH}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\"\nelse\n PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"$PODS_ROOT/FirebaseCrashlytics/run\"\nfi\n\n# Command to upload symbols script used to upload symbols to Firebase server\nflutterfire upload-crashlytics-symbols --upload-symbols-script-path=\"$PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT\" --platform=ios --apple-project-path=\"${SRCROOT}\" --env-platform-name=\"${PLATFORM_NAME}\" --env-configuration=\"${CONFIGURATION}\" --env-project-dir=\"${PROJECT_DIR}\" --env-built-products-dir=\"${BUILT_PRODUCTS_DIR}\" --env-dwarf-dsym-folder-path=\"${DWARF_DSYM_FOLDER_PATH}\" --env-dwarf-dsym-file-name=\"${DWARF_DSYM_FILE_NAME}\" --env-infoplist-path=\"${INFOPLIST_PATH}\" --default-config=default\n"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; diff --git a/lib/config.dart b/lib/config.dart new file mode 100644 index 0000000..cfe42f3 --- /dev/null +++ b/lib/config.dart @@ -0,0 +1,24 @@ +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; +} diff --git a/lib/main_screen.dart b/lib/main_screen.dart index 08e0155..21ad27f 100644 --- a/lib/main_screen.dart +++ b/lib/main_screen.dart @@ -1,15 +1,10 @@ -import 'package:app_settings/app_settings.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:traccar_client/main.dart'; -import 'package:traccar_client/password_service.dart'; import 'package:traccar_client/preferences.dart'; -import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg; import 'l10n/app_localizations.dart'; -import 'status_screen.dart'; -import 'settings_screen.dart'; class MainScreen extends StatefulWidget { const MainScreen({super.key}); @@ -74,144 +69,76 @@ class _MainScreenState extends State { } } - Widget _buildTrackingCard() { - return Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListTile( - contentPadding: EdgeInsets.zero, - title: Text(AppLocalizations.of(context)!.trackingTitle), - titleTextStyle: Theme.of(context).textTheme.headlineMedium, - ), - ListTile( - contentPadding: EdgeInsets.zero, - title: Text(AppLocalizations.of(context)!.idLabel), - subtitle: Text(Preferences.instance.getString(Preferences.id) ?? ''), - ), - SwitchListTile( - contentPadding: EdgeInsets.zero, - title: Text(AppLocalizations.of(context)!.trackingLabel), - value: trackingEnabled, - activeTrackColor: isMoving == false ? Theme.of(context).colorScheme.secondary : null, - onChanged: (bool value) async { - if (await PasswordService.authenticate(context) && mounted) { - if (value) { - try { - FirebaseCrashlytics.instance.log('tracking_toggle_start'); - await bg.BackgroundGeolocation.start(); - if (mounted) { - _checkBatteryOptimizations(context); - } - } on PlatformException catch (error) { - final providerState = await bg.BackgroundGeolocation.providerState; - final isPermissionError = providerState.status == bg.ProviderChangeEvent.AUTHORIZATION_STATUS_DENIED || - providerState.status == bg.ProviderChangeEvent.AUTHORIZATION_STATUS_RESTRICTED; + @override + Widget build(BuildContext context) { + final deviceId = Preferences.instance.getString(Preferences.id) ?? ''; + return Scaffold( + appBar: AppBar( + title: const Text('Traccar Client'), + ), + body: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Card( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + children: [ + Text( + 'Device ID', + style: Theme.of(context).textTheme.labelLarge, + ), + const SizedBox(height: 8), + SelectableText( + deviceId, + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + fontFamily: 'monospace', + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + const SizedBox(height: 16), + Card( + child: SwitchListTile( + title: const Text('Continuous Tracking'), + subtitle: Text( + trackingEnabled + ? 'Sending location updates' + : 'Location tracking disabled', + ), + value: trackingEnabled, + activeTrackColor: isMoving == false + ? Theme.of(context).colorScheme.error + : null, + onChanged: (bool value) async { + if (value) { + try { + await bg.BackgroundGeolocation.start(); + if (mounted) { + _checkBatteryOptimizations(context); + } + } on PlatformException catch (error) { if (!mounted) return; messengerKey.currentState?.showSnackBar( SnackBar( content: Text(error.message ?? error.code), - duration: const Duration(seconds: 4), - action: isPermissionError - ? SnackBarAction( - label: AppLocalizations.of(context)!.settingsTitle, - onPressed: () => AppSettings.openAppSettings( - type: AppSettingsType.settings, - ), - ) - : null, ), ); - } - } else { - FirebaseCrashlytics.instance.log('tracking_toggle_stop'); - bg.BackgroundGeolocation.stop(); - } - } - }, - ), - const SizedBox(height: 8), - OverflowBar( - spacing: 8, - children: [ - FilledButton.tonal( - onPressed: () async { - try { - await bg.BackgroundGeolocation.getCurrentPosition(samples: 1, persist: true, extras: {'manual': true}); - } on PlatformException catch (error) { - messengerKey.currentState?.showSnackBar(SnackBar(content: Text(error.message ?? error.code))); + } + } else { + bg.BackgroundGeolocation.stop(); } }, - child: Text(AppLocalizations.of(context)!.locationButton), ), - FilledButton.tonal( - onPressed: () { - Navigator.push(context, MaterialPageRoute(builder: (_) => const StatusScreen())); - }, - child: Text(AppLocalizations.of(context)!.statusButton), - ), - ], - ), - ], - ), - ), - ); - } - - Widget _buildSettingsCard() { - return Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListTile( - contentPadding: EdgeInsets.zero, - title: Text(AppLocalizations.of(context)!.settingsTitle), - titleTextStyle: Theme.of(context).textTheme.headlineMedium, - ), - ListTile( - contentPadding: EdgeInsets.zero, - title: Text(AppLocalizations.of(context)!.urlLabel), - subtitle: Text(Preferences.instance.getString(Preferences.url) ?? ''), - ), - const SizedBox(height: 8), - OverflowBar( - spacing: 8, - children: [ - FilledButton.tonal( - onPressed: () async { - if (await PasswordService.authenticate(context) && mounted) { - await Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())); - setState(() {}); - } - }, - child: Text(AppLocalizations.of(context)!.settingsButton), - ), - ], - ), - ] - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Traccar Client'), - ), - body: SingleChildScrollView( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - _buildTrackingCard(), - const SizedBox(height: 16), - _buildSettingsCard(), - ], + ), + ], + ), ), ), ); diff --git a/lib/preferences.dart b/lib/preferences.dart index 44ff9d0..4c5230d 100644 --- a/lib/preferences.dart +++ b/lib/preferences.dart @@ -5,6 +5,8 @@ import 'package:flutter_background_geolocation/flutter_background_geolocation.da import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences_android/shared_preferences_android.dart'; +import 'config.dart'; + class Preferences { static Future? _initFuture; static late SharedPreferencesWithCache instance; @@ -47,37 +49,35 @@ class Preferences { ); if (instance.getString(id) == null) { await instance.setString(id, (Random().nextInt(90000000) + 10000000).toString()); - await instance.setString(url, 'http://demo.traccar.org:5055'); - await instance.setString(accuracy, 'medium'); - await instance.setInt(interval, 300); - await instance.setInt(distance, 75); - await instance.setBool(buffer, true); - await instance.setBool(stopDetection, true); - await instance.setInt(fastestInterval, 30); } + 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); } static bg.Config geolocationConfig() { - final isHighestAccuracy = instance.getString(accuracy) == 'highest'; - final locationUpdateInterval = (instance.getInt(interval) ?? 0) * 1000; - final fastestLocationUpdateInterval = (instance.getInt(fastestInterval) ?? 30) * 1000; + final isHighestAccuracy = AppConfig.accuracy == 'highest'; + final locationUpdateInterval = isHighestAccuracy ? 0 : AppConfig.intervalSeconds * 1000; + final fastestLocationUpdateInterval = isHighestAccuracy ? 0 : AppConfig.fastestIntervalSeconds * 1000; final heartbeatInterval = instance.getInt(heartbeat) ?? 0; return bg.Config( isMoving: true, geolocation: bg.GeoConfig( - desiredAccuracy: switch (instance.getString(accuracy)) { + desiredAccuracy: switch (AppConfig.accuracy) { 'highest' => Platform.isIOS ? bg.DesiredAccuracy.navigation : bg.DesiredAccuracy.high, 'high' => bg.DesiredAccuracy.high, 'low' => bg.DesiredAccuracy.low, _ => bg.DesiredAccuracy.medium, }, - distanceFilter: isHighestAccuracy ? 0 : instance.getInt(distance)?.toDouble(), - locationUpdateInterval: Platform.isAndroid - ? (isHighestAccuracy ? 0 : (locationUpdateInterval > 0 ? locationUpdateInterval : null)) - : null, - fastestLocationUpdateInterval: Platform.isAndroid ? (isHighestAccuracy ? 0 : fastestLocationUpdateInterval) : null, + distanceFilter: AppConfig.distanceFilter.toDouble(), + locationUpdateInterval: Platform.isAndroid ? locationUpdateInterval : null, + fastestLocationUpdateInterval: Platform.isAndroid ? fastestLocationUpdateInterval : null, disableElasticity: true, - pausesLocationUpdatesAutomatically: Platform.isIOS ? !(isHighestAccuracy || instance.getBool(stopDetection) == false) : null, + pausesLocationUpdatesAutomatically: Platform.isIOS ? false : null, showsBackgroundLocationIndicator: Platform.isIOS ? false : null, ), app: bg.AppConfig( @@ -102,7 +102,7 @@ class Preferences { ), http: bg.HttpConfig( autoSync: false, - url: _formatUrl(instance.getString(url)), + url: _formatUrl(AppConfig.serverUrl), params: { 'device_id': instance.getString(id), }, @@ -112,10 +112,10 @@ class Preferences { logMaxDays: 1, ), activity: bg.ActivityConfig( - disableStopDetection: instance.getBool(stopDetection) == false, + disableStopDetection: !AppConfig.stopDetection, ), persistence: bg.PersistenceConfig( - maxRecordsToPersist: instance.getBool(buffer) != false ? -1 : 1, + maxRecordsToPersist: AppConfig.buffer ? -1 : 1, locationTemplate: _locationTemplate(), ), ); diff --git a/lib/settings_screen.dart b/lib/settings_screen.dart index ccc8b1c..cac0f2c 100644 --- a/lib/settings_screen.dart +++ b/lib/settings_screen.dart @@ -1,14 +1,9 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg; import 'package:traccar_client/main.dart'; -import 'package:traccar_client/password_service.dart'; -import 'package:traccar_client/qr_code_screen.dart'; -import 'package:wakelock_partial_android/wakelock_partial_android.dart'; -import 'l10n/app_localizations.dart'; +import 'config.dart'; import 'preferences.dart'; class SettingsScreen extends StatefulWidget { @@ -19,223 +14,81 @@ class SettingsScreen extends StatefulWidget { } class _SettingsScreenState extends State { - bool advanced = false; + bool trackingEnabled = false; - String _getAccuracyLabel(String? key) { - return switch (key) { - 'highest' => AppLocalizations.of(context)!.highestAccuracyLabel, - 'high' => AppLocalizations.of(context)!.highAccuracyLabel, - 'low' => AppLocalizations.of(context)!.lowAccuracyLabel, - _ => AppLocalizations.of(context)!.mediumAccuracyLabel, - }; + @override + void initState() { + super.initState(); + _initState(); } - Future _editSetting(String title, String key, bool isInt) async { - final initialValue = isInt - ? Preferences.instance.getInt(key)?.toString() ?? '0' - : Preferences.instance.getString(key) ?? ''; - - final controller = TextEditingController(text: initialValue); - final errorMessage = AppLocalizations.of(context)!.invalidValue; - - final result = await showDialog( - context: context, - builder: (context) => AlertDialog( - scrollable: true, - title: Text(title), - content: TextField( - controller: controller, - keyboardType: isInt ? TextInputType.number : TextInputType.text, - inputFormatters: isInt ? [FilteringTextInputFormatter.digitsOnly] : [], - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancelButton), - ), - TextButton( - onPressed: () => Navigator.pop(context, controller.text), - child: Text(AppLocalizations.of(context)!.saveButton), - ), - ], - ), - ); - - if (result != null && result.isNotEmpty) { - if (key == Preferences.url) { - final uri = Uri.tryParse(result); - if (uri == null || uri.host.isEmpty || !(uri.scheme == 'http' || uri.scheme == 'https')) { - messengerKey.currentState?.showSnackBar(SnackBar(content: Text(errorMessage))); - return; - } - } - if (isInt) { - int? intValue = int.tryParse(result); - if (intValue != null) { - if (key == Preferences.heartbeat && intValue > 0 && intValue < 60) { - intValue = 60; // minimum heartbeat is 60 seconds - } - await Preferences.instance.setInt(key, intValue); - } - } else { - await Preferences.instance.setString(key, result); - } - await bg.BackgroundGeolocation.setConfig(Preferences.geolocationConfig()); - setState(() {}); - } - } - - Future _changePassword() async { - final controller = TextEditingController(); - final result = await showDialog( - context: context, - builder: (context) => AlertDialog( - scrollable: true, - content: TextField( - controller: controller, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.passwordLabel), - obscureText: true, - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: Text(AppLocalizations.of(context)!.cancelButton), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: Text(AppLocalizations.of(context)!.saveButton), - ), - ], - ), - ); - if (result == true) { - await PasswordService.setPassword(controller.text); - } - } - - Widget _buildListTile(String title, String key, bool isInt) { - String? value; - if (isInt) { - final intValue = Preferences.instance.getInt(key); - if (intValue != null && intValue > 0) { - value = intValue.toString(); - } else { - value = AppLocalizations.of(context)!.disabledValue; - } - } else { - value = Preferences.instance.getString(key); - } - return ListTile( - title: Text(title), - subtitle: Text(value ?? ''), - onTap: () => _editSetting(title, key, isInt), - ); - } - - Widget _buildAccuracyListTile() { - final accuracyOptions = ['highest', 'high', 'medium', 'low']; - return ListTile( - title: Text(AppLocalizations.of(context)!.accuracyLabel), - subtitle: Text(_getAccuracyLabel(Preferences.instance.getString(Preferences.accuracy))), - onTap: () async { - final selectedAccuracy = await showDialog( - context: context, - builder: (context) => SimpleDialog( - title: Text(AppLocalizations.of(context)!.accuracyLabel), - children: accuracyOptions.map((option) => SimpleDialogOption( - child: Text(_getAccuracyLabel(option)), - onPressed: () => Navigator.pop(context, option), - )).toList(), - ), - ); - if (selectedAccuracy != null) { - await Preferences.instance.setString(Preferences.accuracy, selectedAccuracy); - await bg.BackgroundGeolocation.setConfig(Preferences.geolocationConfig()); - setState(() {}); - } - }, - ); + void _initState() async { + final state = await bg.BackgroundGeolocation.state; + setState(() { + trackingEnabled = state.enabled; + }); + bg.BackgroundGeolocation.onEnabledChange((bool enabled) { + setState(() { + trackingEnabled = enabled; + }); + }); } @override Widget build(BuildContext context) { - final isHighestAccuracy = Preferences.instance.getString(Preferences.accuracy) == 'highest'; - final distance = Preferences.instance.getInt(Preferences.distance); return Scaffold( appBar: AppBar( - title: Text(AppLocalizations.of(context)!.settingsTitle), - actions: [ - IconButton( - icon: const Icon(Icons.qr_code_scanner), - onPressed: () async { - await Navigator.push(context, MaterialPageRoute(builder: (_) => const QrCodeScreen())); - setState(() {}); - }, - ), - ], + title: const Text('Settings'), ), body: ListView( children: [ - _buildListTile(AppLocalizations.of(context)!.idLabel, Preferences.id, false), - _buildListTile(AppLocalizations.of(context)!.urlLabel, Preferences.url, false), - _buildAccuracyListTile(), - _buildListTile(AppLocalizations.of(context)!.distanceLabel, Preferences.distance, true), - if (isHighestAccuracy || Platform.isAndroid && distance == 0) - _buildListTile(AppLocalizations.of(context)!.intervalLabel, Preferences.interval, true), - if (isHighestAccuracy) - _buildListTile(AppLocalizations.of(context)!.angleLabel, Preferences.angle, true), - _buildListTile(AppLocalizations.of(context)!.heartbeatLabel, Preferences.heartbeat, true), + ListTile( + title: const Text('Device ID'), + subtitle: Text(Preferences.instance.getString(Preferences.id) ?? ''), + enabled: false, + ), SwitchListTile( - title: Text(AppLocalizations.of(context)!.advancedLabel), - value: advanced, - onChanged: (value) { - setState(() => advanced = value); + title: const Text('Continuous Tracking'), + value: trackingEnabled, + onChanged: (bool value) async { + if (value) { + try { + await bg.BackgroundGeolocation.start(); + } on PlatformException catch (error) { + messengerKey.currentState?.showSnackBar( + SnackBar(content: Text(error.message ?? error.code)), + ); + } + } else { + bg.BackgroundGeolocation.stop(); + } }, ), - if (advanced) - _buildListTile(AppLocalizations.of(context)!.fastestIntervalLabel, Preferences.fastestInterval, true), - if (advanced) - SwitchListTile( - title: Text(AppLocalizations.of(context)!.bufferLabel), - value: Preferences.instance.getBool(Preferences.buffer) ?? true, - onChanged: (value) async { - await Preferences.instance.setBool(Preferences.buffer, value); - await bg.BackgroundGeolocation.setConfig(Preferences.geolocationConfig()); - setState(() {}); - }, - ), - if (advanced && Platform.isAndroid) - SwitchListTile( - title: Text(AppLocalizations.of(context)!.wakelockLabel), - value: Preferences.instance.getBool(Preferences.wakelock) ?? false, - onChanged: (value) async { - await Preferences.instance.setBool(Preferences.wakelock, value); - if (value) { - final state = await bg.BackgroundGeolocation.state; - if (state.isMoving == true) { - WakelockPartialAndroid.acquire(); - } - } else { - WakelockPartialAndroid.release(); - } - setState(() {}); - }, - ), - if (advanced) - SwitchListTile( - title: Text(AppLocalizations.of(context)!.stopDetectionLabel), - value: Preferences.instance.getBool(Preferences.stopDetection) ?? true, - onChanged: (value) async { - await Preferences.instance.setBool(Preferences.stopDetection, value); - await bg.BackgroundGeolocation.setConfig(Preferences.geolocationConfig()); - setState(() {}); - }, - ), - if (advanced) - ListTile( - title: Text(AppLocalizations.of(context)!.passwordLabel), - onTap: _changePassword, - ), + ListTile( + title: const Text('Server'), + subtitle: Text(AppConfig.serverUrl), + enabled: false, + ), + ListTile( + title: const Text('Location Accuracy'), + subtitle: Text(AppConfig.accuracy), + enabled: false, + ), + ListTile( + title: const Text('Distance'), + subtitle: Text(AppConfig.distanceFilter == 0 ? 'Disabled' : '${AppConfig.distanceFilter}'), + enabled: false, + ), + ListTile( + title: const Text('Interval'), + subtitle: Text('${AppConfig.intervalSeconds} seconds'), + enabled: false, + ), + ListTile( + title: const Text('Stop Detection'), + subtitle: Text(AppConfig.stopDetection ? 'Enabled' : 'Disabled'), + enabled: false, + ), ], ), ); diff --git a/pubspec.lock b/pubspec.lock index f6e234c..1c2a317 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -441,10 +441,10 @@ packages: dependency: transitive description: name: matcher - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.18" + version: "0.12.19" material_color_utilities: dependency: transitive description: @@ -726,10 +726,10 @@ packages: dependency: transitive description: name: test_api - sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.9" + version: "0.7.10" typed_data: dependency: transitive description: