mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-06 00:25:43 +00:00
feat: Background download on iOS (#1442)
This commit is contained in:
1 parent
9d3221ebbb
commit
99924296ef
14 files changed
+383
-26
No files matched your search
@@ -21,6 +21,8 @@
|
||||
D0E623A929482D160001185E /* Libgopeed.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E623A729482D030001185E /* Libgopeed.xcframework */; };
|
||||
D0E623AA29482D160001185E /* Libgopeed.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0E623A729482D030001185E /* Libgopeed.xcframework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
DCA5B0FA95A8007ADEDACA1B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70BC8D37FAC641C7F20125E8 /* Pods_Runner.framework */; };
|
||||
A10000000000000000000001 /* LocationKeepAliveManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10000000000000000000002 /* LocationKeepAliveManager.swift */;};
|
||||
A10000000000000000000002 /* LocationKeepAliveManager.swift */ = {isa = PBXFileReference;lastKnownFileType = sourcecode.swift;path = LocationKeepAliveManager.swift;sourceTree = "<group>";};
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -153,18 +155,19 @@
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0C585CC72D41E36800FF2EC0 /* Runner.entitlements */,
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
97C147021CF9000F007C117D /* Info.plist */,
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0C585CC72D41E36800FF2EC0 /* Runner.entitlements */,
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
97C147021CF9000F007C117D /* Info.plist */,
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
A10000000000000000000002 /* LocationKeepAliveManager.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -376,12 +379,13 @@
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
A10000000000000000000001 /* LocationKeepAliveManager.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
@@ -32,6 +32,34 @@ import Libgopeed
|
||||
}
|
||||
})
|
||||
|
||||
let locationChannel = FlutterMethodChannel(
|
||||
name: "gopeed/location_keep_alive",
|
||||
binaryMessenger: controller.binaryMessenger
|
||||
)
|
||||
|
||||
locationChannel.setMethodCallHandler {
|
||||
(call: FlutterMethodCall, result: @escaping FlutterResult) in
|
||||
|
||||
switch call.method {
|
||||
|
||||
case "requestPermission":
|
||||
LocationKeepAliveManager.shared.requestPermission { authorized in
|
||||
result(authorized)
|
||||
}
|
||||
|
||||
case "start":
|
||||
LocationKeepAliveManager.shared.start()
|
||||
result(nil)
|
||||
|
||||
case "stop":
|
||||
LocationKeepAliveManager.shared.stop()
|
||||
result(nil)
|
||||
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
|
||||
SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback(registerPlugins)
|
||||
|
||||
@@ -106,5 +106,15 @@
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>location</string>
|
||||
</array>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Please go to Settings and change the location permission to "Always" so that the app can continue running in the background.</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string>Please go to Settings and change the location permission to "Always" so that the app can continue running in the background.</string>
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
<string>Please go to Settings and change the location permission to "Always" so that the app can continue running in the background.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,126 @@
|
||||
import Foundation
|
||||
import CoreLocation
|
||||
|
||||
final class LocationKeepAliveManager: NSObject, CLLocationManagerDelegate {
|
||||
|
||||
static let shared = LocationKeepAliveManager()
|
||||
|
||||
private let locationManager = CLLocationManager()
|
||||
|
||||
private(set) var isRunning = false
|
||||
|
||||
private var permissionCompletion: ((Bool) -> Void)?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
locationManager.delegate = self
|
||||
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
|
||||
locationManager.distanceFilter = kCLDistanceFilterNone
|
||||
locationManager.pausesLocationUpdatesAutomatically = false
|
||||
|
||||
if #available(iOS 9.0, *) {
|
||||
locationManager.allowsBackgroundLocationUpdates = true
|
||||
}
|
||||
|
||||
if #available(iOS 14.0, *) {
|
||||
locationManager.showsBackgroundLocationIndicator = true
|
||||
}
|
||||
}
|
||||
|
||||
func requestPermission(completion: @escaping (Bool) -> Void) {
|
||||
let status = CLLocationManager.authorizationStatus()
|
||||
|
||||
switch status {
|
||||
case .authorizedAlways:
|
||||
completion(true)
|
||||
|
||||
case .authorizedWhenInUse:
|
||||
completion(false)
|
||||
|
||||
case .denied, .restricted:
|
||||
completion(false)
|
||||
|
||||
case .notDetermined:
|
||||
permissionCompletion = completion
|
||||
locationManager.requestAlwaysAuthorization()
|
||||
|
||||
@unknown default:
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func start() {
|
||||
guard !isRunning else {
|
||||
return
|
||||
}
|
||||
|
||||
let status = CLLocationManager.authorizationStatus()
|
||||
|
||||
guard status == .authorizedAlways else {
|
||||
return
|
||||
}
|
||||
|
||||
if #available(iOS 9.0, *) {
|
||||
locationManager.allowsBackgroundLocationUpdates = true
|
||||
}
|
||||
|
||||
locationManager.startUpdatingLocation()
|
||||
isRunning = true
|
||||
}
|
||||
|
||||
func stop() {
|
||||
locationManager.stopUpdatingLocation()
|
||||
isRunning = false
|
||||
}
|
||||
|
||||
private func handleAuthorizationStatus(
|
||||
_ status: CLAuthorizationStatus
|
||||
) {
|
||||
switch status {
|
||||
case .authorizedAlways:
|
||||
permissionCompletion?(true)
|
||||
permissionCompletion = nil
|
||||
|
||||
case .authorizedWhenInUse,
|
||||
.denied,
|
||||
.restricted:
|
||||
permissionCompletion?(false)
|
||||
permissionCompletion = nil
|
||||
|
||||
case .notDetermined:
|
||||
break
|
||||
|
||||
@unknown default:
|
||||
permissionCompletion?(false)
|
||||
permissionCompletion = nil
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
func locationManagerDidChangeAuthorization(
|
||||
_ manager: CLLocationManager
|
||||
) {
|
||||
handleAuthorizationStatus(manager.authorizationStatus)
|
||||
}
|
||||
|
||||
func locationManager(
|
||||
_ manager: CLLocationManager,
|
||||
didChangeAuthorization status: CLAuthorizationStatus
|
||||
) {
|
||||
handleAuthorizationStatus(status)
|
||||
}
|
||||
|
||||
func locationManager(
|
||||
_ manager: CLLocationManager,
|
||||
didUpdateLocations locations: [CLLocation]
|
||||
) {
|
||||
}
|
||||
|
||||
func locationManager(
|
||||
_ manager: CLLocationManager,
|
||||
didFailWithError error: Error
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,17 @@ void init(String network, String address, String apiToken) {
|
||||
_client = _Client(network, address, apiToken);
|
||||
}
|
||||
|
||||
/// Used by LocationKeepAliveCoordinator to reconcile keep-alive
|
||||
void Function()? _onTaskChanged;
|
||||
|
||||
void setTaskChangedListener(void Function() listener) {
|
||||
_onTaskChanged = listener;
|
||||
}
|
||||
|
||||
void _notifyTaskChanged() {
|
||||
_onTaskChanged?.call();
|
||||
}
|
||||
|
||||
Future<T> _parse<T>(
|
||||
Future<Response> Function() fetch,
|
||||
T Function(dynamic json)? fromJsonT,
|
||||
@@ -130,15 +141,19 @@ Future<ResolveResult> resolve(ResolveTask resolveTask) async {
|
||||
}
|
||||
|
||||
Future<String> createTask(CreateTask createTask) async {
|
||||
return _parse<String>(
|
||||
final result = await _parse<String>(
|
||||
() => _client.dio.post("api/v1/tasks", data: createTask),
|
||||
(data) => data as String);
|
||||
_notifyTaskChanged();
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<List<String>> createTaskBatch(CreateTaskBatch createTaskBatch) async {
|
||||
return _parse<List<String>>(
|
||||
final result = await _parse<List<String>>(
|
||||
() => _client.dio.post("api/v1/tasks/batch", data: createTaskBatch),
|
||||
(data) => (data as List).map((e) => e as String).toList());
|
||||
_notifyTaskChanged();
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<void> patchTask(String id, ResolveTask patchTask) async {
|
||||
@@ -154,41 +169,47 @@ Future<List<Task>> getTasks(List<Status> statuses) async {
|
||||
}
|
||||
|
||||
Future<void> pauseTask(String id) async {
|
||||
return _parse(() => _client.dio.put("api/v1/tasks/$id/pause"), null);
|
||||
await _parse(() => _client.dio.put("api/v1/tasks/$id/pause"), null);
|
||||
_notifyTaskChanged();
|
||||
}
|
||||
|
||||
Future<void> continueTask(String id) async {
|
||||
return _parse(() => _client.dio.put("api/v1/tasks/$id/continue"), null);
|
||||
await _parse(() => _client.dio.put("api/v1/tasks/$id/continue"), null);
|
||||
_notifyTaskChanged();
|
||||
}
|
||||
|
||||
Future<void> pauseAllTasks(List<String>? ids) async {
|
||||
return _parse(
|
||||
await _parse(
|
||||
() => _client.dio.put("api/v1/tasks/pause", queryParameters: {
|
||||
"id": ids,
|
||||
}),
|
||||
null);
|
||||
_notifyTaskChanged();
|
||||
}
|
||||
|
||||
Future<void> continueAllTasks(List<String>? ids) async {
|
||||
return _parse(
|
||||
await _parse(
|
||||
() => _client.dio.put("api/v1/tasks/continue", queryParameters: {
|
||||
"id": ids,
|
||||
}),
|
||||
null);
|
||||
_notifyTaskChanged();
|
||||
}
|
||||
|
||||
Future<void> deleteTask(String id, bool force) async {
|
||||
return _parse(
|
||||
await _parse(
|
||||
() => _client.dio.delete("api/v1/tasks/$id?force=$force"), null);
|
||||
_notifyTaskChanged();
|
||||
}
|
||||
|
||||
Future<void> deleteTasks(List<String>? ids, bool force) async {
|
||||
return _parse(
|
||||
await _parse(
|
||||
() => _client.dio.delete("api/v1/tasks", queryParameters: {
|
||||
"id": ids,
|
||||
"force": force,
|
||||
}),
|
||||
null);
|
||||
_notifyTaskChanged();
|
||||
}
|
||||
|
||||
Future<DownloaderConfig> getConfig() async {
|
||||
|
||||
@@ -113,6 +113,7 @@ class ExtraConfig {
|
||||
bool notifyWhenNewVersion;
|
||||
bool autoStartTasks;
|
||||
bool desktopNotification;
|
||||
bool backgroundLocationKeepAlive;
|
||||
List<DownloadCategory> downloadCategories;
|
||||
|
||||
ExtraConfigBt bt = ExtraConfigBt();
|
||||
@@ -127,6 +128,7 @@ class ExtraConfig {
|
||||
this.notifyWhenNewVersion = true,
|
||||
this.autoStartTasks = false,
|
||||
this.desktopNotification = true,
|
||||
this.backgroundLocationKeepAlive = false,
|
||||
this.downloadCategories = const [],
|
||||
});
|
||||
|
||||
|
||||
@@ -111,6 +111,8 @@ ExtraConfig _$ExtraConfigFromJson(Map<String, dynamic> json) => ExtraConfig(
|
||||
notifyWhenNewVersion: json['notifyWhenNewVersion'] as bool? ?? true,
|
||||
autoStartTasks: json['autoStartTasks'] as bool? ?? false,
|
||||
desktopNotification: json['desktopNotification'] as bool? ?? true,
|
||||
backgroundLocationKeepAlive:
|
||||
json['backgroundLocationKeepAlive'] as bool? ?? false,
|
||||
downloadCategories: (json['downloadCategories'] as List<dynamic>?)
|
||||
?.map((e) => DownloadCategory.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
@@ -130,6 +132,7 @@ Map<String, dynamic> _$ExtraConfigToJson(ExtraConfig instance) =>
|
||||
'notifyWhenNewVersion': instance.notifyWhenNewVersion,
|
||||
'autoStartTasks': instance.autoStartTasks,
|
||||
'desktopNotification': instance.desktopNotification,
|
||||
'backgroundLocationKeepAlive': instance.backgroundLocationKeepAlive,
|
||||
'downloadCategories':
|
||||
instance.downloadCategories.map((e) => e.toJson()).toList(),
|
||||
'bt': instance.bt.toJson(),
|
||||
|
||||
@@ -32,6 +32,8 @@ import '../../../views/outlined_button_loading.dart';
|
||||
import '../../../views/text_button_loading.dart';
|
||||
import '../../app/controllers/app_controller.dart';
|
||||
import '../controllers/setting_controller.dart';
|
||||
import '../../../services/location_keep_alive.dart';
|
||||
import '../../../services/location_keep_alive_coordinator.dart';
|
||||
|
||||
const _padding = SizedBox(height: 10);
|
||||
final _divider = const Divider().paddingOnly(left: 10, right: 10);
|
||||
@@ -521,6 +523,44 @@ class SettingView extends GetView<SettingController> {
|
||||
);
|
||||
});
|
||||
|
||||
final buildBackgroundLocationKeepAlive = !Platform.isIOS
|
||||
? () => const SizedBox.shrink()
|
||||
: _buildConfigItem(
|
||||
'backgroundLocationKeepAlive',
|
||||
() {
|
||||
return appController
|
||||
.downloaderConfig.value.extra.backgroundLocationKeepAlive
|
||||
? 'on'.tr
|
||||
: 'off'.tr;
|
||||
},
|
||||
(Key key) {
|
||||
return Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Switch(
|
||||
value: appController
|
||||
.downloaderConfig.value.extra.backgroundLocationKeepAlive,
|
||||
onChanged: (bool value) async {
|
||||
if (value) {
|
||||
final authorized =
|
||||
await LocationKeepAlive.requestPermission();
|
||||
|
||||
if (!authorized) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
appController.downloaderConfig.update((val) {
|
||||
val!.extra.backgroundLocationKeepAlive = value;
|
||||
});
|
||||
|
||||
await debounceSave();
|
||||
await Get.find<LocationKeepAliveCoordinator>().reconcile();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// http config items start
|
||||
final httpConfig = downloaderCfg.value.protocolConfig.http;
|
||||
final buildHttpUa =
|
||||
@@ -1748,6 +1788,7 @@ class SettingView extends GetView<SettingController> {
|
||||
buildAutoStartup(),
|
||||
buildMenubarMode(),
|
||||
buildDesktopNotification(),
|
||||
buildBackgroundLocationKeepAlive(),
|
||||
]),
|
||||
)),
|
||||
Text('archives'.tr),
|
||||
|
||||
@@ -50,4 +50,5 @@ abstract class TaskListController extends GetxController {
|
||||
tasks.sort(compare);
|
||||
this.tasks.value = tasks;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//for ios
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class LocationKeepAlive {
|
||||
static const _channel = MethodChannel('gopeed/location_keep_alive');
|
||||
|
||||
static Future<bool> requestPermission() async {
|
||||
if (!Platform.isIOS) return false;
|
||||
|
||||
try {
|
||||
final result = await _channel.invokeMethod<bool>('requestPermission');
|
||||
|
||||
return result ?? false;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> start() async {
|
||||
if (!Platform.isIOS) return;
|
||||
|
||||
await _channel.invokeMethod('start');
|
||||
}
|
||||
|
||||
static Future<void> stop() async {
|
||||
if (!Platform.isIOS) return;
|
||||
|
||||
await _channel.invokeMethod('stop');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import 'dart:async';
|
||||
import 'package:get/get.dart';
|
||||
import '../../../api/api.dart' as api;
|
||||
import '../../../api/model/task.dart';
|
||||
import '../modules/app/controllers/app_controller.dart';
|
||||
import 'location_keep_alive.dart';
|
||||
|
||||
class LocationKeepAliveCoordinator extends GetxService {
|
||||
Timer? _pollTimer;
|
||||
bool _reconciling = false;
|
||||
|
||||
/// Overridable for testing, defaults to reading from AppController.
|
||||
bool Function() keepAliveEnabledProvider = () => Get.find<AppController>()
|
||||
.downloaderConfig
|
||||
.value
|
||||
.extra
|
||||
.backgroundLocationKeepAlive;
|
||||
|
||||
/// Overridable for testing so polling tests don't need to wait 10s.
|
||||
Duration pollInterval = const Duration(seconds: 10);
|
||||
|
||||
bool get _keepAliveEnabled => keepAliveEnabledProvider();
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
api.setTaskChangedListener(() {
|
||||
reconcile();
|
||||
});
|
||||
}
|
||||
|
||||
Future<int> _fetchGlobalRunningCount() async {
|
||||
try {
|
||||
final tasks = await api.getTasks([Status.running]);
|
||||
return tasks.length;
|
||||
} catch (_) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> reconcile() async {
|
||||
if (_reconciling) return;
|
||||
_reconciling = true;
|
||||
try {
|
||||
if (!_keepAliveEnabled) {
|
||||
await LocationKeepAlive.stop();
|
||||
return;
|
||||
}
|
||||
final count = await _fetchGlobalRunningCount();
|
||||
if (count < 0) return;
|
||||
if (count > 0) {
|
||||
await LocationKeepAlive.start();
|
||||
} else {
|
||||
await LocationKeepAlive.stop();
|
||||
}
|
||||
} finally {
|
||||
_reconciling = false;
|
||||
}
|
||||
}
|
||||
|
||||
void startPolling() {
|
||||
_pollTimer?.cancel();
|
||||
_pollTimer = Timer.periodic(pollInterval, (_) => reconcile());
|
||||
}
|
||||
|
||||
void stopPolling() {
|
||||
_pollTimer?.cancel();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
stopPolling();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
@@ -226,6 +226,7 @@ const enUS = {
|
||||
'ed2kServerMetHint': 'Server.met URL or ed2k serverlist link, one per line',
|
||||
'ed2kNodesDatHint': 'nodes.dat path or URL, one per line',
|
||||
'desktopNotification': 'Desktop Notifications',
|
||||
'backgroundLocationKeepAlive': 'Background Download Keep Alive',
|
||||
'notificationTaskDone': 'Task completed',
|
||||
'notificationTaskError': 'Task error',
|
||||
},
|
||||
|
||||
@@ -221,6 +221,7 @@ const zhCN = {
|
||||
'ed2kServerMetHint': '请输入 Server.met 地址或 ed2k 服务器列表链接,每行一条',
|
||||
'ed2kNodesDatHint': '请输入 nodes.dat 本地路径或 URL,每行一条',
|
||||
'desktopNotification': '桌面通知',
|
||||
'backgroundLocationKeepAlive': '常驻后台',
|
||||
'notificationTaskDone': '任务完成',
|
||||
'notificationTaskError': '任务失败',
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import 'util/package_info.dart';
|
||||
import 'util/scheme_register/scheme_register.dart';
|
||||
import 'util/updater.dart';
|
||||
import 'util/util.dart';
|
||||
import 'app/services/location_keep_alive_coordinator.dart';
|
||||
|
||||
class StartupArgs {
|
||||
static const flagHidden = "hidden";
|
||||
@@ -135,6 +136,17 @@ Future<void> init(StartupArgs args) async {
|
||||
logger.w("auto-start tasks fail", e);
|
||||
}
|
||||
}
|
||||
|
||||
// iOS location keep-alive coordinator
|
||||
if (Util.isIOS()) {
|
||||
try {
|
||||
await Get.putAsync(() async => LocationKeepAliveCoordinator());
|
||||
await Get.find<LocationKeepAliveCoordinator>().reconcile();
|
||||
Get.find<LocationKeepAliveCoordinator>().startPolling();
|
||||
} catch (e) {
|
||||
logger.e("location keep-alive coordinator init fail", e);
|
||||
}
|
||||
}
|
||||
|
||||
() async {
|
||||
if (Util.isDesktop()) {
|
||||
|
||||
Reference in new issue
Block a user