feat: Background download on iOS (#1442)

This commit is contained in:
Afterain authored and GitHub committed 2026-08-18 18:08:53 +08:00
1 parent 9d3221ebbb
commit 99924296ef
14 files changed
+383 -26

No files matched your search

+22 -18
View File
@@ -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 */
+28
View File
@@ -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)
+10
View File
@@ -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
) {
}
}