Ignore URLError.cancelled so pull-to-refresh doesn't show a spurious error; bump to 3.5
Build iOS App / Build .ipa and publish source (push) Successful in 1m44s
Build iOS App / Build .ipa and publish source (push) Successful in 1m44s
Pull-to-refresh cancels the still-in-flight request (URLError.cancelled, localizedDescription 'cancelled') to run the refresh, which surfaced as an error dialog. Add an isCancellation helper and skip cancellations in the PR load and CI-runs paths. Bump to 3.5.
This commit is contained in:
1 parent
a045a36125
commit
6ed50ecc4a
2 files changed
+21
-5
No files matched your search
@@ -187,7 +187,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 25;
|
CURRENT_PROJECT_VERSION = 26;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "GiteaPR";
|
INFOPLIST_KEY_CFBundleDisplayName = "GiteaPR";
|
||||||
@@ -197,7 +197,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 3.4;
|
MARKETING_VERSION = 3.5;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.bueso.giteapr;
|
PRODUCT_BUNDLE_IDENTIFIER = com.bueso.giteapr;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -211,7 +211,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 25;
|
CURRENT_PROJECT_VERSION = 26;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "GiteaPR";
|
INFOPLIST_KEY_CFBundleDisplayName = "GiteaPR";
|
||||||
@@ -221,7 +221,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 3.4;
|
MARKETING_VERSION = 3.5;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.bueso.giteapr;
|
PRODUCT_BUNDLE_IDENTIFIER = com.bueso.giteapr;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
|||||||
@@ -36,6 +36,18 @@ final class PRDetailViewModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A task cancellation is not a real failure — it happens when a refresh
|
||||||
|
/// (pull-to-refresh / .task / .onAppear) supersedes an in-flight request,
|
||||||
|
/// and surfaces as URLError.cancelled ("cancelled"). These must not be
|
||||||
|
/// shown as errors.
|
||||||
|
private func isCancellation(_ error: Error) -> Bool {
|
||||||
|
if let urlError = error as? URLError {
|
||||||
|
return urlError.code == .cancelled
|
||||||
|
}
|
||||||
|
let ns = error as NSError
|
||||||
|
return ns.domain == NSURLErrorDomain && ns.code == NSURLErrorCancelled
|
||||||
|
}
|
||||||
|
|
||||||
func load() async {
|
func load() async {
|
||||||
guard let client = SettingsStore.shared.client() else { return }
|
guard let client = SettingsStore.shared.client() else { return }
|
||||||
isLoading = true
|
isLoading = true
|
||||||
@@ -48,6 +60,7 @@ final class PRDetailViewModel: ObservableObject {
|
|||||||
do {
|
do {
|
||||||
pr = try await client.pullRequest(owner: owner, repo: repo.name, number: prNumber)
|
pr = try await client.pullRequest(owner: owner, repo: repo.name, number: prNumber)
|
||||||
} catch {
|
} catch {
|
||||||
|
if isCancellation(error) { return } // pull-to-refresh superseded; not a real error
|
||||||
LogStore.shared.log(.error, "PR \(prNumber): load failed: \(error.localizedDescription)")
|
LogStore.shared.log(.error, "PR \(prNumber): load failed: \(error.localizedDescription)")
|
||||||
errorMessage = error.localizedDescription
|
errorMessage = error.localizedDescription
|
||||||
return
|
return
|
||||||
@@ -79,10 +92,13 @@ final class PRDetailViewModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// CI is best-effort; don't fail the whole detail view, but log it so
|
// CI is best-effort; don't fail the whole detail view, but log it so
|
||||||
// a token-scope 403 on the Actions endpoint is visible.
|
// a token-scope 403 on the Actions endpoint is visible. Ignore
|
||||||
|
// cancellations (refresh superseding an in-flight request).
|
||||||
|
if !isCancellation(error) {
|
||||||
LogStore.shared.log(.warn, "PR \(prNumber): actions/runs failed: \(error.localizedDescription)")
|
LogStore.shared.log(.warn, "PR \(prNumber): actions/runs failed: \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func rerun(runID: Int64) async {
|
func rerun(runID: Int64) async {
|
||||||
guard let client = SettingsStore.shared.client() else { return }
|
guard let client = SettingsStore.shared.client() else { return }
|
||||||
|
|||||||
Reference in new issue
Block a user