2025-05-05 22:43:43 -07:00
|
|
|
import 'dart:io';
|
|
|
|
|
import 'dart:math';
|
|
|
|
|
|
2025-05-07 17:42:07 -07:00
|
|
|
import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg;
|
2025-05-05 22:43:43 -07:00
|
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
2025-06-03 22:27:07 -07:00
|
|
|
import 'package:shared_preferences_android/shared_preferences_android.dart';
|
2025-05-05 22:43:43 -07:00
|
|
|
|
2026-03-06 12:32:44 +11:00
|
|
|
import 'config.dart';
|
|
|
|
|
|
2025-05-05 22:43:43 -07:00
|
|
|
class Preferences {
|
2026-02-08 09:06:50 -08:00
|
|
|
static Future<void>? _initFuture;
|
2025-06-03 22:27:07 -07:00
|
|
|
static late SharedPreferencesWithCache instance;
|
2025-06-16 22:43:21 -07:00
|
|
|
|
2025-05-05 22:43:43 -07:00
|
|
|
static const String id = 'id';
|
|
|
|
|
static const String url = 'url';
|
|
|
|
|
static const String accuracy = 'accuracy';
|
|
|
|
|
static const String distance = 'distance';
|
2025-06-16 21:15:38 -07:00
|
|
|
static const String interval = 'interval';
|
|
|
|
|
static const String angle = 'angle';
|
2025-06-11 17:47:43 -07:00
|
|
|
static const String heartbeat = 'heartbeat';
|
2025-06-16 21:15:38 -07:00
|
|
|
static const String fastestInterval = 'fastest_interval';
|
2025-05-05 22:43:43 -07:00
|
|
|
static const String buffer = 'buffer';
|
2025-06-14 12:54:33 -07:00
|
|
|
static const String wakelock = 'wakelock';
|
2025-06-11 22:21:50 -07:00
|
|
|
static const String stopDetection = 'stop_detection';
|
2026-02-21 07:01:36 -08:00
|
|
|
static const String password = 'password';
|
2025-05-05 22:43:43 -07:00
|
|
|
|
2025-06-16 22:43:21 -07:00
|
|
|
static const String lastTimestamp = 'lastTimestamp';
|
|
|
|
|
static const String lastLatitude = 'lastLatitude';
|
|
|
|
|
static const String lastLongitude = 'lastLongitude';
|
|
|
|
|
static const String lastHeading = 'lastHeading';
|
|
|
|
|
|
2025-05-05 22:43:43 -07:00
|
|
|
static Future<void> init() async {
|
2026-02-08 09:06:50 -08:00
|
|
|
_initFuture ??= _createInstance();
|
|
|
|
|
await _initFuture;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Future<void> _createInstance() async {
|
2025-06-03 22:27:07 -07:00
|
|
|
instance = await SharedPreferencesWithCache.create(
|
|
|
|
|
sharedPreferencesOptions: Platform.isAndroid
|
|
|
|
|
? SharedPreferencesAsyncAndroidOptions(backend: SharedPreferencesAndroidBackendLibrary.SharedPreferences)
|
|
|
|
|
: SharedPreferencesOptions(),
|
|
|
|
|
cacheOptions: SharedPreferencesWithCacheOptions(
|
|
|
|
|
allowList: {
|
2025-06-16 21:15:38 -07:00
|
|
|
id, url, accuracy, distance, interval, angle, heartbeat,
|
2026-02-21 07:01:36 -08:00
|
|
|
fastestInterval, buffer, wakelock, stopDetection, password,
|
2025-06-16 22:43:21 -07:00
|
|
|
lastTimestamp, lastLatitude, lastLongitude, lastHeading,
|
2025-06-03 22:27:07 -07:00
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
2026-02-21 07:01:36 -08:00
|
|
|
if (instance.getString(id) == null) {
|
|
|
|
|
await instance.setString(id, (Random().nextInt(90000000) + 10000000).toString());
|
2025-05-05 22:43:43 -07:00
|
|
|
}
|
2026-03-06 12:32:44 +11:00
|
|
|
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);
|
2025-05-05 22:43:43 -07:00
|
|
|
}
|
|
|
|
|
|
2025-05-10 09:51:51 -07:00
|
|
|
static bg.Config geolocationConfig() {
|
2026-03-06 12:32:44 +11:00
|
|
|
final isHighestAccuracy = AppConfig.accuracy == 'highest';
|
|
|
|
|
final locationUpdateInterval = isHighestAccuracy ? 0 : AppConfig.intervalSeconds * 1000;
|
|
|
|
|
final fastestLocationUpdateInterval = isHighestAccuracy ? 0 : AppConfig.fastestIntervalSeconds * 1000;
|
2025-06-11 22:21:50 -07:00
|
|
|
final heartbeatInterval = instance.getInt(heartbeat) ?? 0;
|
2025-05-07 17:42:07 -07:00
|
|
|
return bg.Config(
|
2025-07-05 09:35:58 -07:00
|
|
|
isMoving: true,
|
2026-01-15 07:27:28 -08:00
|
|
|
geolocation: bg.GeoConfig(
|
2026-03-06 12:32:44 +11:00
|
|
|
desiredAccuracy: switch (AppConfig.accuracy) {
|
2026-01-15 07:27:28 -08:00
|
|
|
'highest' => Platform.isIOS ? bg.DesiredAccuracy.navigation : bg.DesiredAccuracy.high,
|
|
|
|
|
'high' => bg.DesiredAccuracy.high,
|
|
|
|
|
'low' => bg.DesiredAccuracy.low,
|
|
|
|
|
_ => bg.DesiredAccuracy.medium,
|
|
|
|
|
},
|
2026-03-06 12:32:44 +11:00
|
|
|
distanceFilter: AppConfig.distanceFilter.toDouble(),
|
|
|
|
|
locationUpdateInterval: Platform.isAndroid ? locationUpdateInterval : null,
|
|
|
|
|
fastestLocationUpdateInterval: Platform.isAndroid ? fastestLocationUpdateInterval : null,
|
2026-01-15 07:27:28 -08:00
|
|
|
disableElasticity: true,
|
2026-03-06 12:32:44 +11:00
|
|
|
pausesLocationUpdatesAutomatically: Platform.isIOS ? false : null,
|
2026-02-22 14:47:11 -08:00
|
|
|
showsBackgroundLocationIndicator: Platform.isIOS ? false : null,
|
2026-01-15 07:27:28 -08:00
|
|
|
),
|
|
|
|
|
app: bg.AppConfig(
|
2026-02-22 14:43:12 -08:00
|
|
|
enableHeadless: Platform.isAndroid ? true : null,
|
2026-01-15 07:27:28 -08:00
|
|
|
stopOnTerminate: false,
|
2026-02-22 14:43:12 -08:00
|
|
|
startOnBoot: Platform.isAndroid ? true : null,
|
2026-01-15 07:27:28 -08:00
|
|
|
heartbeatInterval: heartbeatInterval > 0 ? heartbeatInterval.toDouble() : null,
|
2026-02-22 14:43:12 -08:00
|
|
|
preventSuspend: Platform.isIOS ? (heartbeatInterval > 0) : null,
|
|
|
|
|
backgroundPermissionRationale: Platform.isAndroid
|
|
|
|
|
? bg.PermissionRationale(
|
|
|
|
|
title: 'Allow {applicationName} to access this device\'s location in the background',
|
|
|
|
|
message: 'For reliable tracking, please enable {backgroundPermissionOptionLabel} location access.',
|
|
|
|
|
positiveAction: 'Change to {backgroundPermissionOptionLabel}',
|
|
|
|
|
negativeAction: 'Cancel')
|
|
|
|
|
: null,
|
|
|
|
|
notification: Platform.isAndroid
|
|
|
|
|
? bg.Notification(
|
|
|
|
|
smallIcon: 'drawable/ic_stat_notify',
|
|
|
|
|
priority: bg.NotificationPriority.low,
|
|
|
|
|
)
|
|
|
|
|
: null,
|
2026-01-15 07:27:28 -08:00
|
|
|
),
|
|
|
|
|
http: bg.HttpConfig(
|
|
|
|
|
autoSync: false,
|
2026-03-06 12:32:44 +11:00
|
|
|
url: _formatUrl(AppConfig.serverUrl),
|
2026-01-15 07:27:28 -08:00
|
|
|
params: {
|
|
|
|
|
'device_id': instance.getString(id),
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
logger: const bg.LoggerConfig(
|
|
|
|
|
logLevel: bg.LogLevel.verbose,
|
|
|
|
|
logMaxDays: 1,
|
|
|
|
|
),
|
|
|
|
|
activity: bg.ActivityConfig(
|
2026-03-06 12:32:44 +11:00
|
|
|
disableStopDetection: !AppConfig.stopDetection,
|
2025-06-15 06:45:54 -07:00
|
|
|
),
|
2026-01-15 07:27:28 -08:00
|
|
|
persistence: bg.PersistenceConfig(
|
2026-03-06 12:32:44 +11:00
|
|
|
maxRecordsToPersist: AppConfig.buffer ? -1 : 1,
|
2026-01-15 07:27:28 -08:00
|
|
|
locationTemplate: _locationTemplate(),
|
2025-07-12 11:45:04 -07:00
|
|
|
),
|
2025-05-07 17:42:07 -07:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-31 21:58:52 -07:00
|
|
|
static String? _formatUrl(String? url) {
|
|
|
|
|
if (url == null) return null;
|
|
|
|
|
final uri = Uri.parse(url);
|
|
|
|
|
if ((uri.path.isEmpty || uri.path == '') && !url.endsWith('/')) return '$url/';
|
|
|
|
|
return url;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static String _locationTemplate() {
|
|
|
|
|
return '''{
|
|
|
|
|
"timestamp": "<%= timestamp %>",
|
|
|
|
|
"coords": {
|
|
|
|
|
"latitude": <%= latitude %>,
|
|
|
|
|
"longitude": <%= longitude %>,
|
|
|
|
|
"accuracy": <%= accuracy %>,
|
|
|
|
|
"speed": <%= speed %>,
|
|
|
|
|
"heading": <%= heading %>,
|
|
|
|
|
"altitude": <%= altitude %>
|
|
|
|
|
},
|
|
|
|
|
"is_moving": <%= is_moving %>,
|
|
|
|
|
"odometer": <%= odometer %>,
|
|
|
|
|
"event": "<%= event %>",
|
|
|
|
|
"battery": {
|
|
|
|
|
"level": <%= battery.level %>,
|
|
|
|
|
"is_charging": <%= battery.is_charging %>
|
|
|
|
|
},
|
|
|
|
|
"activity": {
|
|
|
|
|
"type": "<%= activity.type %>"
|
|
|
|
|
},
|
|
|
|
|
"extras": {},
|
|
|
|
|
"_": "&id=${instance.getString(id)}&lat=<%= latitude %>&lon=<%= longitude %>×tamp=<%= timestamp %>&"
|
|
|
|
|
}'''.split('\n').map((line) => line.trimLeft()).join();
|
|
|
|
|
}
|
2025-05-05 22:43:43 -07:00
|
|
|
}
|