UI General fix#4086
Conversation
Signed-off-by: Marino Faggiana <marino.faggiana@nextcloud.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the app’s banner error “de-duplication” logic by replacing the previous global shownErrors tracking with a time-based, per-error (optionally per-account) cooldown gate, and bumps the app’s marketing version.
Changes:
- Bump
MARKETING_VERSIONto33.0.8in the Xcode project. - Remove the global
shownErrorsmechanism and its clearing points in app lifecycle/networking. - Introduce
ErrorBannerGate(actor) and use it to throttle repeated banners in the Lucid Banner helpers.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| Nextcloud.xcodeproj/project.pbxproj | Updates version/build settings (marketing version + project version). |
| iOSClient/Networking/NCNetworkingProcess.swift | Removes foreground-session error clearing tied to the old shownErrors mechanism. |
| iOSClient/NCAppStateManager.swift | Removes global shownErrors and background clear of that state. |
| iOSClient/GUI/Lucid Banner/WarningBannerView.swift | Switches banner spam prevention from bannerContainsError to ErrorBannerGate. |
| iOSClient/GUI/Lucid Banner/ShowBanner.swift | Switches banner spam prevention from bannerContainsError to ErrorBannerGate. |
| iOSClient/GUI/Lucid Banner/InfoBannerView.swift | Switches banner spam prevention from bannerContainsError to ErrorBannerGate. |
| iOSClient/GUI/Lucid Banner/HelperBanner.swift | Adds ErrorBannerGate actor implementing cooldown-based gating. |
| iOSClient/GUI/Lucid Banner/ErrorBannerView.swift | Switches error banner gating to ErrorBannerGate (and removes previous cancellation/skip handling). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; | ||
| COPY_PHASE_STRIP = NO; | ||
| CURRENT_PROJECT_VERSION = 5; | ||
| CURRENT_PROJECT_VERSION = 0; |
| CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; | ||
| COPY_PHASE_STRIP = NO; | ||
| CURRENT_PROJECT_VERSION = 5; | ||
| CURRENT_PROJECT_VERSION = 0; |
| if let errorCode, | ||
| let controller = SceneManager.shared.getController(scene: windowScene) { | ||
| if await !ErrorBannerGate.shared.shouldShow(errorCode: errorCode, account: controller.account) { |
| func showErrorBanner(windowScene: UIWindowScene?, | ||
| title: String = "_error_", | ||
| text: String, | ||
| footnote: String? = nil, | ||
| errorCode: Int? = nil, | ||
| afError: AFError? = nil) async { | ||
| guard let windowScene, | ||
| let window = windowScene.windows.first(where: \.isKeyWindow) else { | ||
| return | ||
| } | ||
|
|
||
| #if !EXTENSION | ||
| guard !bannerContainsError(errorCode: errorCode, afError: afError) else { | ||
| return | ||
| if let errorCode, | ||
| let controller = SceneManager.shared.getController(scene: windowScene) { | ||
| if await !ErrorBannerGate.shared.shouldShow(errorCode: errorCode, account: controller.account) { | ||
| return | ||
| } | ||
| } |
| if let errorCode, | ||
| let controller = SceneManager.shared.getController(scene: windowScene) { | ||
| if await !ErrorBannerGate.shared.shouldShow(errorCode: errorCode, account: controller.account) { |
| if let errorCode, | ||
| let controller = SceneManager.shared.getController(scene: windowScene) { | ||
| if await !ErrorBannerGate.shared.shouldShow(errorCode: errorCode, account: controller.account) { |
| if let errorCode, | ||
| let controller = SceneManager.shared.getController(scene: windowScene) { | ||
| if await !ErrorBannerGate.shared.shouldShow(errorCode: errorCode, account: controller.account) { |
There was a problem hiding this comment.
Pull request overview
This PR refactors banner deduplication to avoid repeatedly showing the same error banners, replacing the previous global shownErrors mechanism with a new ErrorBannerGate actor-based cooldown approach, and updates the banner presentation entry points to consult this gate.
Changes:
- Remove the global
shownErrorsstate and its clearing logic from app lifecycle/networking code. - Add
ErrorBannerGateactor with per-error (optionally per-account) cooldown behavior and use it from banner presentation helpers. - Bump
MARKETING_VERSIONto33.0.8(but also changes build number settings).
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| iOSClient/Networking/NCNetworkingProcess.swift | Removes legacy clearing of the old shownErrors set. |
| iOSClient/NCAppStateManager.swift | Removes global shownErrors definition and background reset logic. |
| iOSClient/GUI/Lucid Banner/WarningBannerView.swift | Switches suppression logic to ErrorBannerGate. |
| iOSClient/GUI/Lucid Banner/ShowBanner.swift | Switches suppression logic to ErrorBannerGate for generic banners. |
| iOSClient/GUI/Lucid Banner/InfoBannerView.swift | Switches suppression logic to ErrorBannerGate. |
| iOSClient/GUI/Lucid Banner/HelperBanner.swift | Introduces the new ErrorBannerGate actor implementation. |
| iOSClient/GUI/Lucid Banner/ErrorBannerView.swift | Switches suppression logic to ErrorBannerGate for error banners. |
| Nextcloud.xcodeproj/project.pbxproj | Updates marketing version and modifies project version/build number settings. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if let errorCode, | ||
| let controller = SceneManager.shared.getController(scene: windowScene) { | ||
| if await !ErrorBannerGate.shared.shouldShow(errorCode: errorCode, account: controller.account) { | ||
| return | ||
| } | ||
| } | ||
| #endif |
| if let errorCode, | ||
| let controller = SceneManager.shared.getController(scene: windowScene) { | ||
| if await !ErrorBannerGate.shared.shouldShow(errorCode: errorCode, account: controller.account) { | ||
| return(nil, nil) | ||
| } | ||
| } | ||
| #endif |
| if let errorCode, | ||
| let controller = SceneManager.shared.getController(scene: windowScene) { | ||
| if await !ErrorBannerGate.shared.shouldShow(errorCode: errorCode, account: controller.account) { | ||
| return | ||
| } | ||
| } | ||
| #endif |
| private func cooldownInterval(for errorCode: Int) -> TimeInterval { | ||
| switch errorCode { | ||
|
|
||
| case NSURLErrorNotConnectedToInternet: | ||
| return 30 // No internet connection (persistent until network changes) | ||
|
|
||
| case NSURLErrorCannotFindHost: | ||
| return 30 // Host/DNS not reachable (likely server down or misconfigured URL) | ||
|
|
||
| case 401: | ||
| return 30 // Unauthorized (server maintenance) | ||
|
|
||
| case 423: | ||
| return 20 // Resource locked (temporary server-side condition) | ||
|
|
||
| case 507: | ||
| return 30 // Insufficient storage (server quota exceeded, persistent) | ||
|
|
||
| default: | ||
| return 5 // Transient or unknown error | ||
| } |
| CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; | ||
| COPY_PHASE_STRIP = NO; | ||
| CURRENT_PROJECT_VERSION = 5; | ||
| CURRENT_PROJECT_VERSION = 0; |
| CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; | ||
| COPY_PHASE_STRIP = NO; | ||
| CURRENT_PROJECT_VERSION = 5; | ||
| CURRENT_PROJECT_VERSION = 0; |
| #if !EXTENSION | ||
| guard !bannerContainsError(errorCode: errorCode, afError: afError) else { | ||
| return | ||
| if let errorCode, | ||
| let controller = SceneManager.shared.getController(scene: windowScene) { | ||
| if await !ErrorBannerGate.shared.shouldShow(errorCode: errorCode, account: controller.account) { | ||
| return | ||
| } | ||
| } | ||
| #endif |
| if let errorCode, | ||
| let controller = SceneManager.shared.getController(scene: windowScene) { | ||
| if await !ErrorBannerGate.shared.shouldShow(errorCode: errorCode, account: controller.account) { | ||
| return | ||
| } | ||
| } | ||
| #endif |
Uh oh!
There was an error while loading. Please reload this page.