mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-07 17:10:29 +00:00
Merge branch 'main' into main
This commit is contained in:
18 files changed
+2983
-3
No files matched your search
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
nativebridge "github.com/GopeedLab/gopeed/bind/native"
|
||||
"github.com/GopeedLab/gopeed/internal/webview/rpcprovider"
|
||||
goapi "github.com/GopeedLab/gopeed/pkg/api"
|
||||
"github.com/GopeedLab/gopeed/pkg/rest"
|
||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||
)
|
||||
@@ -90,3 +91,14 @@ func applyWebViewProvider(config *model.StartConfig) {
|
||||
}
|
||||
config.WebViewProvider = rpcprovider.New(*config.WebViewRPCConfig)
|
||||
}
|
||||
|
||||
func LiveActivityTaskEventMask() int64 {
|
||||
return int64(
|
||||
goapi.TaskEventDone |
|
||||
goapi.TaskEventError |
|
||||
goapi.TaskEventStart |
|
||||
goapi.TaskEventProgress |
|
||||
goapi.TaskEventPause |
|
||||
goapi.TaskEventDelete,
|
||||
)
|
||||
}
|
||||
@@ -10,8 +10,15 @@ import (
|
||||
type TaskEventMask uint64
|
||||
|
||||
const (
|
||||
// Keep these first two unchanged because Flutter already uses
|
||||
// bit 0 for done and bit 1 for error.
|
||||
TaskEventDone TaskEventMask = 1 << iota
|
||||
TaskEventError
|
||||
|
||||
TaskEventStart
|
||||
TaskEventProgress
|
||||
TaskEventPause
|
||||
TaskEventDelete
|
||||
)
|
||||
|
||||
type TaskEvent struct {
|
||||
@@ -42,11 +49,26 @@ func (s *Service) emitTaskEvent(event *download.Event) {
|
||||
}
|
||||
|
||||
var mask TaskEventMask
|
||||
|
||||
switch event.Key {
|
||||
case download.EventKeyDone:
|
||||
mask = TaskEventDone
|
||||
|
||||
case download.EventKeyError:
|
||||
mask = TaskEventError
|
||||
|
||||
case download.EventKeyStart:
|
||||
mask = TaskEventStart
|
||||
|
||||
case download.EventKeyProgress:
|
||||
mask = TaskEventProgress
|
||||
|
||||
case download.EventKeyPause:
|
||||
mask = TaskEventPause
|
||||
|
||||
case download.EventKeyDelete:
|
||||
mask = TaskEventDelete
|
||||
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
import ActivityKit
|
||||
import SwiftUI
|
||||
import WidgetKit
|
||||
|
||||
@main
|
||||
struct GopeedLiveActivityBundle: WidgetBundle {
|
||||
var body: some Widget {
|
||||
GopeedLiveActivityWidget()
|
||||
}
|
||||
}
|
||||
|
||||
struct GopeedLiveActivityWidget: Widget {
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
|
||||
ActivityConfiguration(
|
||||
for: GopeedDownloadAttributes.self
|
||||
) { context in
|
||||
|
||||
// MARK: Lock Screen / notification banner
|
||||
|
||||
VStack(spacing: 10) {
|
||||
|
||||
HStack {
|
||||
|
||||
Image(systemName: statusIcon(context.state.status))
|
||||
.font(.title3)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
|
||||
Text(context.attributes.fileName)
|
||||
.font(.headline)
|
||||
.lineLimit(1)
|
||||
|
||||
Text(statusText(context.state.status))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(
|
||||
"\(Int(context.state.progress * 100))%"
|
||||
)
|
||||
.font(.headline)
|
||||
.monospacedDigit()
|
||||
}
|
||||
|
||||
progressView(context)
|
||||
|
||||
HStack {
|
||||
|
||||
Text(
|
||||
"\(formatBytes(context.state.downloaded)) / \(formatBytes(context.state.total))"
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
if context.state.speed > 0 {
|
||||
Text(
|
||||
"\(formatBytes(context.state.speed))/s"
|
||||
)
|
||||
}
|
||||
}
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.padding()
|
||||
.activityBackgroundTint(.black.opacity(0.85))
|
||||
.activitySystemActionForegroundColor(.white)
|
||||
|
||||
} dynamicIsland: { context in
|
||||
|
||||
DynamicIsland {
|
||||
|
||||
// MARK: Expanded Dynamic Island
|
||||
|
||||
DynamicIslandExpandedRegion(.leading) {
|
||||
Image(
|
||||
systemName: statusIcon(
|
||||
context.state.status
|
||||
)
|
||||
)
|
||||
.font(.title3)
|
||||
}
|
||||
|
||||
DynamicIslandExpandedRegion(.center) {
|
||||
Text(context.attributes.fileName)
|
||||
.lineLimit(1)
|
||||
.font(.headline)
|
||||
}
|
||||
|
||||
DynamicIslandExpandedRegion(.trailing) {
|
||||
Text(
|
||||
"\(Int(context.state.progress * 100))%"
|
||||
)
|
||||
.monospacedDigit()
|
||||
.font(.headline)
|
||||
}
|
||||
|
||||
DynamicIslandExpandedRegion(.bottom) {
|
||||
|
||||
VStack(spacing: 6) {
|
||||
|
||||
progressView(context)
|
||||
|
||||
HStack {
|
||||
|
||||
Text(
|
||||
"\(formatBytes(context.state.downloaded)) / \(formatBytes(context.state.total))"
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
if context.state.speed > 0 {
|
||||
Text(
|
||||
"\(formatBytes(context.state.speed))/s"
|
||||
)
|
||||
}
|
||||
}
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
} compactLeading: {
|
||||
|
||||
Image(
|
||||
systemName: statusIcon(
|
||||
context.state.status
|
||||
)
|
||||
)
|
||||
|
||||
} compactTrailing: {
|
||||
|
||||
Text(
|
||||
"\(Int(context.state.progress * 100))%"
|
||||
)
|
||||
.monospacedDigit()
|
||||
|
||||
} minimal: {
|
||||
|
||||
Image(systemName: "arrow.down")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Progress bar
|
||||
|
||||
@ViewBuilder
|
||||
private func progressView(
|
||||
_ context:
|
||||
ActivityViewContext<GopeedDownloadAttributes>
|
||||
) -> some View {
|
||||
|
||||
if context.state.usesEstimatedProgress,
|
||||
context.state.estimatedEnd >
|
||||
context.state.estimatedStart {
|
||||
|
||||
ProgressView(
|
||||
timerInterval:
|
||||
context.state.estimatedStart
|
||||
...
|
||||
context.state.estimatedEnd,
|
||||
countsDown: false
|
||||
)
|
||||
|
||||
} else {
|
||||
|
||||
ProgressView(
|
||||
value: context.state.progress,
|
||||
total: 1.0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Formatting
|
||||
|
||||
private func formatBytes(
|
||||
_ bytes: Int64
|
||||
) -> String {
|
||||
|
||||
guard bytes > 0 else {
|
||||
return "0 B"
|
||||
}
|
||||
|
||||
return ByteCountFormatter.string(
|
||||
fromByteCount: bytes,
|
||||
countStyle: .file
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private func statusIcon(
|
||||
_ status: String
|
||||
) -> String {
|
||||
|
||||
switch status {
|
||||
|
||||
case "done":
|
||||
return "checkmark.circle.fill"
|
||||
|
||||
case "error":
|
||||
return "exclamationmark.triangle.fill"
|
||||
|
||||
case "pause", "paused":
|
||||
return "pause.circle.fill"
|
||||
|
||||
default:
|
||||
return "arrow.down.circle.fill"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func statusText(
|
||||
_ status: String
|
||||
) -> String {
|
||||
|
||||
switch status {
|
||||
|
||||
case "done":
|
||||
return "Download complete"
|
||||
|
||||
case "error":
|
||||
return "Download failed"
|
||||
|
||||
case "pause", "paused":
|
||||
return "Paused"
|
||||
|
||||
default:
|
||||
return "Downloading"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Gopeed</string>
|
||||
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.widgetkit-extension</string>
|
||||
</dict>
|
||||
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -7,13 +7,19 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
032447D610CFAF8A75642212 /* GopeedLiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB8C142A455C80367DDD68AE /* GopeedLiveActivityManager.swift */; };
|
||||
0C585CBA2D41E28900FF2EC0 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C585CB92D41E28900FF2EC0 /* ShareViewController.swift */; };
|
||||
0C585CBD2D41E28900FF2EC0 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 0C585CBC2D41E28900FF2EC0 /* Base */; };
|
||||
0C585CC12D41E28900FF2EC0 /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0C585CB72D41E28900FF2EC0 /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
1045ACE898D35C0D8D8A993E /* GopeedDownloadAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AC32C4BF90EAC54D048584 /* GopeedDownloadAttributes.swift */; };
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
1E29080079AAFD1CF2C71262 /* GopeedDownloadAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AC32C4BF90EAC54D048584 /* GopeedDownloadAttributes.swift */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
3EF29839EA13551EB8144269 /* GopeedLiveActivityExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 76ED41E0163DFA97F018D1AC /* GopeedLiveActivityExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
5BF69191A426106A38B62F9F /* GopeedContinuedProcessingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3879B6DB5398A19281BA5F8F /* GopeedContinuedProcessingManager.swift */; };
|
||||
6085D504396791024CEC496B /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8823E31DA135C410E362683E /* Pods_RunnerTests.framework */; };
|
||||
6243BE8F995F5284DBDD7DE3 /* GopeedLiveActivityWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C9F8DE4B991051758F2BE1 /* GopeedLiveActivityWidget.swift */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
@@ -30,6 +36,13 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
03B416C78831516F6591BD24 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = FFA6D07C1B1CB56543DE4B67;
|
||||
remoteInfo = GopeedLiveActivityExtension;
|
||||
};
|
||||
0C585CBF2D41E28900FF2EC0 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
@@ -54,6 +67,7 @@
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
0C585CC12D41E28900FF2EC0 /* ShareExtension.appex in Embed Foundation Extensions */,
|
||||
3EF29839EA13551EB8144269 /* GopeedLiveActivityExtension.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -72,6 +86,7 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
09AC32C4BF90EAC54D048584 /* GopeedDownloadAttributes.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GopeedDownloadAttributes.swift; sourceTree = "<group>"; };
|
||||
0C585CB72D41E28900FF2EC0 /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0C585CB92D41E28900FF2EC0 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||
0C585CBC2D41E28900FF2EC0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||
@@ -85,11 +100,15 @@
|
||||
3217CFC346202B3E2A8C52DD /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
35C9F8DE4B991051758F2BE1 /* GopeedLiveActivityWidget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GopeedLiveActivityWidget.swift; sourceTree = "<group>"; };
|
||||
3879B6DB5398A19281BA5F8F /* GopeedContinuedProcessingManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GopeedContinuedProcessingManager.swift; sourceTree = "<group>"; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
52A13633B52EDC9D3F479DFD /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
55356FCC7E573C3FEEC2DF4D /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
5B918B8D5281BAE2DEA6BE3C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
76ED41E0163DFA97F018D1AC /* GopeedLiveActivityExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = GopeedLiveActivityExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
78EE8538B1DF04DAF73E75D3 /* Pods-ShareExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.profile.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
78F0D48FED63C04B9A3FFDE4 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
@@ -112,6 +131,7 @@
|
||||
D554135997A5C7A2C62E1B64 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
D081C25B294826C0006EB10B /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
|
||||
D0E623A729482D030001185E /* Libgopeed.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Libgopeed.xcframework; path = Frameworks/Libgopeed.xcframework; sourceTree = "<group>"; };
|
||||
EB8C142A455C80367DDD68AE /* GopeedLiveActivityManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GopeedLiveActivityManager.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -123,6 +143,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
2134C1710299C602C39EE9D6 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -194,6 +221,16 @@
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5FB22DCA9F2EA11B7C1F1A22 /* GopeedLiveActivity */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
35C9F8DE4B991051758F2BE1 /* GopeedLiveActivityWidget.swift */,
|
||||
52A13633B52EDC9D3F479DFD /* Info.plist */,
|
||||
);
|
||||
name = GopeedLiveActivity;
|
||||
path = GopeedLiveActivity;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -215,6 +252,7 @@
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
2B6C6F082D376286EF3D5FA3 /* Pods */,
|
||||
44BA5D7C75A3E4957F6560C7 /* Frameworks */,
|
||||
5FB22DCA9F2EA11B7C1F1A22 /* GopeedLiveActivity */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -224,6 +262,7 @@
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
0C585CB72D41E28900FF2EC0 /* ShareExtension.appex */,
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||
76ED41E0163DFA97F018D1AC /* GopeedLiveActivityExtension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -244,6 +283,9 @@
|
||||
A20000000000000000000003 /* GopeedTaskEventForwarder.h */,
|
||||
A20000000000000000000002 /* GopeedTaskEventForwarder.m */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
09AC32C4BF90EAC54D048584 /* GopeedDownloadAttributes.swift */,
|
||||
EB8C142A455C80367DDD68AE /* GopeedLiveActivityManager.swift */,
|
||||
3879B6DB5398A19281BA5F8F /* GopeedContinuedProcessingManager.swift */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
@@ -306,12 +348,30 @@
|
||||
);
|
||||
dependencies = (
|
||||
0C585CC02D41E28900FF2EC0 /* PBXTargetDependency */,
|
||||
FBEBBD3469D2B152FDD0B424 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
FFA6D07C1B1CB56543DE4B67 /* GopeedLiveActivityExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DAF208923F589C87CBB49D56 /* Build configuration list for PBXNativeTarget "GopeedLiveActivityExtension" */;
|
||||
buildPhases = (
|
||||
1A1326FD8C4C86ED5A529666 /* Sources */,
|
||||
2134C1710299C602C39EE9D6 /* Frameworks */,
|
||||
9AB02E797D34C8878FDBED22 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = GopeedLiveActivityExtension;
|
||||
productName = GopeedLiveActivityExtension;
|
||||
productReference = 76ED41E0163DFA97F018D1AC /* GopeedLiveActivityExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@@ -351,6 +411,7 @@
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
0C585CB62D41E28900FF2EC0 /* ShareExtension */,
|
||||
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||
FFA6D07C1B1CB56543DE4B67 /* GopeedLiveActivityExtension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -382,6 +443,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
9AB02E797D34C8878FDBED22 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
@@ -510,6 +578,15 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
1A1326FD8C4C86ED5A529666 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6243BE8F995F5284DBDD7DE3 /* GopeedLiveActivityWidget.swift in Sources */,
|
||||
1045ACE898D35C0D8D8A993E /* GopeedDownloadAttributes.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
331C807D294A63A400263BE5 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -527,6 +604,9 @@
|
||||
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */,
|
||||
A10000000000000000000001 /* LocationKeepAliveManager.swift in Sources */,
|
||||
A20000000000000000000001 /* GopeedTaskEventForwarder.m in Sources */,
|
||||
1E29080079AAFD1CF2C71262 /* GopeedDownloadAttributes.swift in Sources */,
|
||||
032447D610CFAF8A75642212 /* GopeedLiveActivityManager.swift in Sources */,
|
||||
5BF69191A426106A38B62F9F /* GopeedContinuedProcessingManager.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -543,6 +623,12 @@
|
||||
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
FBEBBD3469D2B152FDD0B424 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = GopeedLiveActivityExtension;
|
||||
target = FFA6D07C1B1CB56543DE4B67 /* GopeedLiveActivityExtension */;
|
||||
targetProxy = 03B416C78831516F6591BD24 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
@@ -774,6 +860,61 @@
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
6CADF5E48DB3AF5F00998F2A /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB31CF90195004384FC /* Generated.xcconfig */;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = NO;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = GopeedLiveActivity/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Gopeed;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed.gopeed.GopeedLiveActivityExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
91F84B2C36416D1F9C500144 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB31CF90195004384FC /* Generated.xcconfig */;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = NO;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = GopeedLiveActivity/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Gopeed;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed.gopeed.GopeedLiveActivityExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147031CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -930,6 +1071,34 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
ABCC87B2280A98A3C9E482CF /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB31CF90195004384FC /* Generated.xcconfig */;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = NO;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = GopeedLiveActivity/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Gopeed;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed.gopeed.GopeedLiveActivityExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
@@ -973,6 +1142,16 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
DAF208923F589C87CBB49D56 /* Build configuration list for PBXNativeTarget "GopeedLiveActivityExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
6CADF5E48DB3AF5F00998F2A /* Release */,
|
||||
91F84B2C36416D1F9C500144 /* Debug */,
|
||||
ABCC87B2280A98A3C9E482CF /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
|
||||
@@ -63,8 +63,19 @@ import UIKit
|
||||
GopeedInvokeAsyncWithResult(method, path, query, body, requestID, result)
|
||||
case "subscribeTaskEvents":
|
||||
let arguments = call.arguments as? [String: Any]
|
||||
let mask = (arguments?["mask"] as? NSNumber)?.int64Value ?? 0
|
||||
GopeedSubscribeTaskEventsWithForwarder(mask, mask == 0 ? nil : taskEventForwarder)
|
||||
let flutterMask =
|
||||
(arguments?["mask"] as? NSNumber)?.int64Value ?? 0
|
||||
|
||||
let liveActivityMask =
|
||||
LibgopeedLiveActivityTaskEventMask()
|
||||
|
||||
let combinedMask =
|
||||
flutterMask | liveActivityMask
|
||||
|
||||
GopeedSubscribeTaskEventsWithForwarder(
|
||||
combinedMask,
|
||||
taskEventForwarder
|
||||
)
|
||||
result(nil)
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
@@ -91,5 +102,55 @@ import UIKit
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
let continuedProcessingChannel =
|
||||
FlutterMethodChannel(
|
||||
name: "gopeed/continued_processing",
|
||||
binaryMessenger: messenger
|
||||
)
|
||||
|
||||
continuedProcessingChannel
|
||||
.setMethodCallHandler { call, result in
|
||||
|
||||
switch call.method {
|
||||
|
||||
case "isSupported":
|
||||
|
||||
if #available(iOS 26.0, *) {
|
||||
result(true)
|
||||
} else {
|
||||
result(false)
|
||||
}
|
||||
|
||||
case "setEnabled":
|
||||
|
||||
let arguments =
|
||||
call.arguments
|
||||
as? [String: Any]
|
||||
|
||||
let enabled =
|
||||
arguments?["enabled"]
|
||||
as? Bool
|
||||
?? false
|
||||
|
||||
if #available(iOS 26.0, *) {
|
||||
|
||||
GopeedContinuedProcessingManager
|
||||
.shared
|
||||
.setEnabled(enabled) {
|
||||
success in
|
||||
result(success)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
result(!enabled)
|
||||
}
|
||||
|
||||
default:
|
||||
result(
|
||||
FlutterMethodNotImplemented
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,28 @@
|
||||
import ActivityKit
|
||||
import Foundation
|
||||
|
||||
@available(iOS 16.2, *)
|
||||
struct GopeedDownloadAttributes: ActivityAttributes {
|
||||
|
||||
struct ContentState: Codable, Hashable {
|
||||
|
||||
// Real confirmed progress.
|
||||
var progress: Double
|
||||
|
||||
var downloaded: Int64
|
||||
var total: Int64
|
||||
var speed: Int64
|
||||
|
||||
var status: String
|
||||
|
||||
// Used by the self-moving estimated progress bar.
|
||||
var estimatedStart: Date
|
||||
var estimatedEnd: Date
|
||||
var usesEstimatedProgress: Bool
|
||||
}
|
||||
|
||||
// Static properties.
|
||||
var taskId: String
|
||||
var fileName: String
|
||||
var generation: String
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -13,6 +13,18 @@ FOUNDATION_EXPORT void GopeedSubscribeTaskEventsWithForwarder(
|
||||
int64_t mask,
|
||||
GopeedTaskEventForwarder * _Nullable listener);
|
||||
|
||||
|
||||
typedef void (^GopeedNativeInvokeCompletion)(
|
||||
BOOL success,
|
||||
NSString *payload);
|
||||
|
||||
FOUNDATION_EXPORT void GopeedInvokeAsyncNative(
|
||||
NSString *method,
|
||||
NSString *path,
|
||||
NSString *query,
|
||||
NSString *body,
|
||||
GopeedNativeInvokeCompletion completion);
|
||||
|
||||
FOUNDATION_EXPORT void GopeedInvokeAsyncWithResult(
|
||||
NSString *method,
|
||||
NSString *path,
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#import <Libgopeed/Libgopeed.h>
|
||||
|
||||
#import "Runner-Swift.h"
|
||||
|
||||
@interface GopeedTaskEventForwarder () <LibgopeedTaskEventListener>
|
||||
|
||||
@property(nonatomic, strong) FlutterMethodChannel *channel;
|
||||
@@ -47,6 +49,45 @@
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface GopeedNativeInvokeResultForwarder
|
||||
: NSObject <LibgopeedInvokeResultListener>
|
||||
|
||||
@property(nonatomic, copy, nullable)
|
||||
GopeedNativeInvokeCompletion completion;
|
||||
|
||||
- (instancetype)initWithCompletion:
|
||||
(GopeedNativeInvokeCompletion)completion;
|
||||
|
||||
@end
|
||||
|
||||
@implementation GopeedNativeInvokeResultForwarder
|
||||
|
||||
- (instancetype)initWithCompletion:
|
||||
(GopeedNativeInvokeCompletion)completion {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_completion = [completion copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onResult:(int64_t)requestID
|
||||
success:(BOOL)success
|
||||
payload:(NSString * _Nullable)payload {
|
||||
GopeedNativeInvokeCompletion completion =
|
||||
self.completion;
|
||||
self.completion = nil;
|
||||
|
||||
if (completion == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
completion(success, payload ?: @"");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GopeedTaskEventForwarder
|
||||
|
||||
- (instancetype)initWithChannel:(FlutterMethodChannel *)channel {
|
||||
@@ -59,9 +100,59 @@
|
||||
|
||||
- (void)onTaskEvent:(NSString * _Nullable)payload {
|
||||
NSString *arguments = payload ?: @"";
|
||||
|
||||
NSData *data =
|
||||
[arguments dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
NSDictionary *json = nil;
|
||||
|
||||
if (data) {
|
||||
json =
|
||||
[NSJSONSerialization JSONObjectWithData:data
|
||||
options:0
|
||||
error:nil];
|
||||
}
|
||||
|
||||
NSString *type = json[@"type"];
|
||||
NSString *taskID = json[@"taskId"];
|
||||
|
||||
BOOL continuedProcessingHandlesTask = NO;
|
||||
|
||||
// iOS 26 system continued-processing Live Activity.
|
||||
if (@available(iOS 26.0, *)) {
|
||||
GopeedContinuedProcessingManager *manager =
|
||||
[GopeedContinuedProcessingManager shared];
|
||||
|
||||
[manager handleTaskEventPayload:arguments];
|
||||
|
||||
if (taskID.length > 0) {
|
||||
continuedProcessingHandlesTask =
|
||||
[manager isHandlingTaskId:taskID];
|
||||
}
|
||||
}
|
||||
|
||||
// When BGCPT is active, let Apple's system Live Activity
|
||||
// represent progress. Otherwise keep using our existing
|
||||
// custom Gopeed ActivityKit implementation.
|
||||
if (!continuedProcessingHandlesTask) {
|
||||
[[GopeedLiveActivityManager shared]
|
||||
handleTaskEventPayload:arguments];
|
||||
}
|
||||
|
||||
// Flutter currently understands only done/error.
|
||||
BOOL flutterEvent =
|
||||
[type isEqualToString:@"task.done"] ||
|
||||
[type isEqualToString:@"task.error"];
|
||||
|
||||
if (!flutterEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
FlutterMethodChannel *channel = self.channel;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[channel invokeMethod:@"taskEvent" arguments:arguments];
|
||||
[channel invokeMethod:@"taskEvent"
|
||||
arguments:arguments];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -84,3 +175,26 @@ void GopeedInvokeAsyncWithResult(
|
||||
[[GopeedInvokeResultForwarder alloc] initWithResult:result];
|
||||
LibgopeedInvokeAsync(method, path, query, body, requestID, forwarder);
|
||||
}
|
||||
|
||||
void GopeedInvokeAsyncNative(
|
||||
NSString *method,
|
||||
NSString *path,
|
||||
NSString *query,
|
||||
NSString *body,
|
||||
GopeedNativeInvokeCompletion completion) {
|
||||
if (completion == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
GopeedNativeInvokeResultForwarder *forwarder =
|
||||
[[GopeedNativeInvokeResultForwarder alloc]
|
||||
initWithCompletion:completion];
|
||||
|
||||
LibgopeedInvokeAsync(
|
||||
method,
|
||||
path,
|
||||
query,
|
||||
body,
|
||||
0,
|
||||
forwarder);
|
||||
}
|
||||
@@ -115,6 +115,7 @@
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>location</string>
|
||||
<string>processing</string>
|
||||
</array>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
@@ -156,5 +157,14 @@
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>NSSupportsLiveActivities</key>
|
||||
<true/>
|
||||
|
||||
<key>NSSupportsLiveActivitiesFrequentUpdates</key>
|
||||
<true/>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).continuedDownload.*</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -145,6 +145,7 @@ class ExtraConfig {
|
||||
bool notifyWhenNewVersion;
|
||||
bool desktopNotification;
|
||||
bool backgroundLocationKeepAlive;
|
||||
bool backgroundContinuedProcessing;
|
||||
WindowStateConfig windowState;
|
||||
Map<String, String> bookmarks;
|
||||
List<String> createHistory;
|
||||
@@ -166,6 +167,7 @@ class ExtraConfig {
|
||||
this.notifyWhenNewVersion = true,
|
||||
this.desktopNotification = true,
|
||||
this.backgroundLocationKeepAlive = false,
|
||||
this.backgroundContinuedProcessing = false,
|
||||
WindowStateConfig? windowState,
|
||||
this.bookmarks = const {},
|
||||
this.createHistory = const [],
|
||||
|
||||
@@ -135,6 +135,10 @@ ExtraConfig _$ExtraConfigFromJson(Map<String, dynamic> json) =>
|
||||
notifyWhenNewVersion: json['notifyWhenNewVersion'] as bool? ?? true,
|
||||
desktopNotification: json['desktopNotification'] as bool? ?? true,
|
||||
backgroundLocationKeepAlive: json['backgroundLocationKeepAlive'] as bool? ?? false,
|
||||
backgroundContinuedProcessing:
|
||||
json['backgroundContinuedProcessing']
|
||||
as bool? ??
|
||||
false,
|
||||
windowState: json['windowState'] == null
|
||||
? null
|
||||
: WindowStateConfig.fromJson(json['windowState'] as Map<String, dynamic>?),
|
||||
@@ -162,6 +166,8 @@ Map<String, dynamic> _$ExtraConfigToJson(ExtraConfig instance) => <String, dynam
|
||||
'notifyWhenNewVersion': instance.notifyWhenNewVersion,
|
||||
'desktopNotification': instance.desktopNotification,
|
||||
'backgroundLocationKeepAlive': instance.backgroundLocationKeepAlive,
|
||||
'backgroundContinuedProcessing':
|
||||
instance.backgroundContinuedProcessing,
|
||||
'windowState': instance.windowState.toJson(),
|
||||
'bookmarks': instance.bookmarks,
|
||||
'createHistory': instance.createHistory,
|
||||
|
||||
@@ -23,6 +23,7 @@ import '../../util/util.dart';
|
||||
import '../rpc/webview_rpc_service.dart';
|
||||
import 'android_foreground_service.dart';
|
||||
import 'location_keep_alive.dart';
|
||||
import 'continued_processing.dart';
|
||||
|
||||
const unixSocketPath = 'gopeed.sock';
|
||||
|
||||
@@ -105,8 +106,14 @@ class AppRuntimeController extends AsyncNotifier<AppRuntimeState> {
|
||||
),
|
||||
);
|
||||
unawaited(LocationKeepAliveCoordinator.instance.reconcile(enabled: config.extra.backgroundLocationKeepAlive));
|
||||
unawaited(
|
||||
ContinuedProcessing.setEnabled(
|
||||
config.extra.backgroundContinuedProcessing,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Future<AppRuntimeState> _init() async {
|
||||
await AppInitializer.ensureStorageInitialized();
|
||||
try {
|
||||
@@ -180,6 +187,13 @@ class AppRuntimeController extends AsyncNotifier<AppRuntimeState> {
|
||||
startupError: startupError,
|
||||
);
|
||||
if (Util.isIOS()) {
|
||||
// Make sure the native Continued Processing manager has the
|
||||
// persisted setting before app initialization completes. This
|
||||
// avoids the first download racing ahead of setEnabled(true).
|
||||
await ContinuedProcessing.setEnabled(
|
||||
config.extra.backgroundContinuedProcessing,
|
||||
);
|
||||
|
||||
LocationKeepAliveCoordinator.instance.start(
|
||||
() =>
|
||||
state.value?.downloaderConfig.extra.backgroundLocationKeepAlive ?? config.extra.backgroundLocationKeepAlive,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class ContinuedProcessing {
|
||||
const ContinuedProcessing._();
|
||||
|
||||
static const _channel =
|
||||
MethodChannel('gopeed/continued_processing');
|
||||
|
||||
static Future<bool> isSupported() async {
|
||||
if (!Platform.isIOS) return false;
|
||||
|
||||
try {
|
||||
return await _channel.invokeMethod<bool>(
|
||||
'isSupported',
|
||||
) ??
|
||||
false;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<bool> setEnabled(
|
||||
bool enabled,
|
||||
) async {
|
||||
if (!Platform.isIOS) return false;
|
||||
|
||||
try {
|
||||
return await _channel.invokeMethod<bool>(
|
||||
'setEnabled',
|
||||
{'enabled': enabled},
|
||||
) ??
|
||||
false;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import '../../../../app/application/app_appearance_controller.dart';
|
||||
import '../../../../app/application/app_platform_controller.dart';
|
||||
import '../../../../app/application/app_runtime_controller.dart';
|
||||
import '../../../../app/application/location_keep_alive.dart';
|
||||
import '../../../../app/application/continued_processing.dart';
|
||||
import '../../../../core/common/start_config.dart';
|
||||
import '../../../../core/network/gopeed/gopeed_transport.dart';
|
||||
import '../../../../core/utils/breakpoints.dart';
|
||||
@@ -381,6 +382,19 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
|
||||
onChanged: (value) => unawaited(_setBackgroundLocationKeepAlive(value)),
|
||||
),
|
||||
),
|
||||
if (Util.isIOS())
|
||||
SettingsItem(
|
||||
title: context.l10n.backgroundContinuedProcessing,
|
||||
subtitle:
|
||||
context.l10n.backgroundContinuedProcessingDescription,
|
||||
child: shad.Switch(
|
||||
value:
|
||||
config.extra.backgroundContinuedProcessing,
|
||||
onChanged: (value) => unawaited(
|
||||
_setBackgroundContinuedProcessing(value),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -1281,6 +1295,43 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
|
||||
_mutateConfig((config) => config.extra.backgroundLocationKeepAlive = enabled);
|
||||
await LocationKeepAliveCoordinator.instance.reconcile(enabled: enabled);
|
||||
}
|
||||
Future<void>
|
||||
_setBackgroundContinuedProcessing(
|
||||
bool enabled,
|
||||
) async {
|
||||
if (enabled &&
|
||||
!await ContinuedProcessing.isSupported()) {
|
||||
if (mounted) {
|
||||
_toast(
|
||||
context.l10n
|
||||
.backgroundContinuedProcessingUnsupported,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final applied =
|
||||
await ContinuedProcessing.setEnabled(
|
||||
enabled,
|
||||
);
|
||||
|
||||
if (enabled && !applied) {
|
||||
if (mounted) {
|
||||
_toast(
|
||||
context.l10n
|
||||
.backgroundContinuedProcessingUnsupported,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_mutateConfig(
|
||||
(config) =>
|
||||
config.extra
|
||||
.backgroundContinuedProcessing =
|
||||
enabled,
|
||||
);
|
||||
}
|
||||
|
||||
List<String> _lines(String text) {
|
||||
return text.split('\n').map((line) => line.trim()).where((line) => line.isNotEmpty).toList();
|
||||
|
||||
@@ -249,6 +249,7 @@
|
||||
"ed2kNodesDatHint": "nodes.dat path or URL, one per line",
|
||||
"desktopNotification": "Desktop Notifications",
|
||||
"backgroundLocationKeepAlive": "Background Downloads",
|
||||
"backgroundContinuedProcessing": "Continued Processing (iOS 26+)",
|
||||
"notificationTaskDone": "Task completed",
|
||||
"notificationTaskError": "Task error",
|
||||
"appTitle": "Gopeed Download Manager",
|
||||
@@ -291,6 +292,8 @@
|
||||
"browserExtensionDescription": "Send browser downloads directly to Gopeed after installation",
|
||||
"desktopNotificationDescription": "Show a system notification when a task completes or fails",
|
||||
"backgroundLocationKeepAliveDescription": "Use location access to keep downloads active in the background",
|
||||
"backgroundContinuedProcessingDescription": "Use iOS continued processing and its system Live Activity to keep downloads running without location access",
|
||||
"backgroundContinuedProcessingUnsupported": "Continued Processing requires iOS 26 or later",
|
||||
"currentVersion": "Current version {version}",
|
||||
"contributors": "Contributors",
|
||||
"viewContributors": "View contributors",
|
||||
|
||||
Reference in new issue
Block a user