[PM-26062] Consolidate Loading Overlay (#2044)

This commit is contained in:
Katherine Bertelsen 2025-10-16 13:02:26 -05:00 committed by GitHub
parent 39b11f17ae
commit c9bc35bf96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 50 additions and 193 deletions

View File

@ -1,3 +1,5 @@
import BitwardenKit
// MARK: - AnyCoordinator
/// A type erased wrapper for a coordinator.

View File

@ -1,8 +0,0 @@
/// State used to configure the display of a `LoadingOverlayView`.
///
public struct LoadingOverlayState: Equatable {
// MARK: Properties
/// The title of the loading overlay, displayed below the activity indicator.
let title: String
}

View File

@ -1,17 +0,0 @@
// swiftlint:disable:this file_name
import SnapshotTesting
import XCTest
@testable import AuthenticatorShared
class LoadingOverlayViewTests: BitwardenTestCase {
// MARK: Tests
/// Test a snapshot of the loading overlay.
func disabletest_snapshot_loadingOverlay() {
assertSnapshots(
of: LoadingOverlayView(state: .init(title: "Loading...")),
as: [.defaultPortrait, .defaultPortraitDark],
)
}
}

View File

@ -1,46 +0,0 @@
import SwiftUI
/// A loading overlay view which shows an activity indicator with text below it.
///
struct LoadingOverlayView: View {
// MARK: Properties
@Environment(\.colorScheme) private var colorScheme
/// The state used to configure the display of the view.
let state: LoadingOverlayState
var body: some View {
ProgressView {
Text(state.title)
.foregroundColor(Asset.Colors.textPrimary.swiftUIColor)
.font(.headline)
.multilineTextAlignment(.center)
}
.padding(16)
.frame(width: 270, alignment: .center)
.background(
ZStack {
Asset.Colors.materialRegularBase.swiftUIColor
Asset.Colors.materialRegularBlend.swiftUIColor
.blendMode(colorScheme == .light ? .colorDodge : .overlay)
}
.compositingGroup(),
)
.cornerRadius(14)
.controlSize(.large)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.background(Asset.Colors.backgroundDimmed.swiftUIColor.ignoresSafeArea())
}
}
#if DEBUG
#Preview {
LoadingOverlayView(state: LoadingOverlayState(title: "Progress..."))
}
#Preview {
LoadingOverlayView(state: LoadingOverlayState(title: "Progress..."))
.preferredColorScheme(.dark)
}
#endif

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import SwiftUI
import XCTest

View File

@ -1,4 +1,5 @@
import AVFoundation
import BitwardenKit
import BitwardenKitMocks
import SwiftUI
import XCTest

View File

@ -3,7 +3,7 @@ import SwiftUI
/// A spinning circular activity indicator.
///
struct CircularActivityIndicator: View {
public struct CircularActivityIndicator: View {
// MARK: Properties
/// Whether the activity indicator is spinning.
@ -14,7 +14,7 @@ struct CircularActivityIndicator: View {
// MARK: View
var body: some View {
public var body: some View {
ZStack {
Circle()
.stroke(SharedAsset.Colors.backgroundTertiary.swiftUIColor, style: strokeStyle)
@ -31,6 +31,11 @@ struct CircularActivityIndicator: View {
}
.frame(width: 56, height: 56)
}
// MARK: Initializers
/// Initializes a `CircularActivityIndicator`.
public init() {}
}
// MARK: - Previews

View File

@ -1,10 +1,10 @@
import BitwardenKit
import SwiftUI
import UIKit
/// A helper to configure showing and hiding the `LoadingOverlayView` within a view controller.
///
enum LoadingOverlayDisplayHelper {
@MainActor
public enum LoadingOverlayDisplayHelper {
// MARK: Type Properties
/// The duration in seconds of the show and hide transitions.
@ -12,7 +12,7 @@ enum LoadingOverlayDisplayHelper {
/// A value that is used to identify the loading overlay view within the view hierarchy in
/// order to remove it.
static let overlayViewTag = 1000
public static let overlayViewTag = 1000
// MARK: Type Methods
@ -22,7 +22,7 @@ enum LoadingOverlayDisplayHelper {
/// - parentViewController: The parent view controller that the overlay should be shown above.
/// - state: State used to configure the display of the loading overlay view.
///
static func show(in parentViewController: UIViewController, state: LoadingOverlayState) {
public static func show(in parentViewController: UIViewController, state: LoadingOverlayState) {
guard parentViewController.view.window?.viewWithTag(overlayViewTag) == nil,
let window = parentViewController.view.window
else {
@ -46,7 +46,7 @@ enum LoadingOverlayDisplayHelper {
///
/// - Parameter parentViewController: The parent view controller that the overlay is shown above.
///
static func hide(from parentViewController: UIViewController) {
public static func hide(from parentViewController: UIViewController) {
guard let view = parentViewController.view.window?.viewWithTag(overlayViewTag) else { return }
UIView.animate(withDuration: UI.duration(transitionDuration)) {

View File

@ -1,10 +1,10 @@
import BitwardenKit
import SwiftUI
import XCTest
@testable import AuthenticatorShared
class LoadingOverlayDisplayHelperTests: BitwardenTestCase {
/// `show(in:state:)` shows the loading overlay in the parent view controller.
@MainActor
func test_show() throws {
let parentViewController = UIViewController()
let window = UIWindow()
@ -25,6 +25,7 @@ class LoadingOverlayDisplayHelperTests: BitwardenTestCase {
}
/// `hide(from:)` hides the loading overlay in the parent view controller.
@MainActor
func test_hide() throws {
let parentViewController = UIViewController()
let window = UIWindow()

View File

@ -0,0 +1,18 @@
/// State used to configure the display of a `LoadingOverlayView`.
///
public struct LoadingOverlayState: Equatable {
// MARK: Properties
/// The title of the loading overlay, displayed below the activity indicator.
public let title: String
// MARK: Initializers
/// Initializes a `LoadingOverlayState`.
///
/// - Parameters:
/// - title: The title of the loading overlay, displayed below the activity indicator.
public init(title: String) {
self.title = title
}
}

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenKitMocks
import BitwardenResources
import TestHelpers

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenKitMocks
import BitwardenResources
import BitwardenSdk

View File

@ -1,3 +1,4 @@
import BitwardenKit
import SwiftUI
// MARK: - LoadingView

View File

@ -1,3 +1,4 @@
import BitwardenKit
import Foundation
// MARK: - AnyCoordinator

View File

@ -1,58 +0,0 @@
import BitwardenKit
import SwiftUI
import UIKit
/// A helper to configure showing and hiding the `LoadingOverlayView` within a view controller.
///
enum LoadingOverlayDisplayHelper {
// MARK: Type Properties
/// The duration in seconds of the show and hide transitions.
static let transitionDuration: TimeInterval = 0.2
/// A value that is used to identify the loading overlay view within the view hierarchy in
/// order to remove it.
static let overlayViewTag = 1000
// MARK: Type Methods
/// Shows the loading overlay view as a full-screen view over the specified view controller.
///
/// - Parameters:
/// - parentViewController: The parent view controller that the overlay should be shown above.
/// - state: State used to configure the display of the loading overlay view.
///
static func show(in parentViewController: UIViewController, state: LoadingOverlayState) {
guard parentViewController.view.window?.viewWithTag(overlayViewTag) == nil,
let window = parentViewController.view.window
else {
return
}
let viewController = UIHostingController(rootView: LoadingOverlayView(state: state))
viewController.view.layer.backgroundColor = nil
viewController.view.layer.opacity = 0
viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
viewController.view.frame = window.frame
viewController.view.tag = overlayViewTag
window.addSubview(viewController.view)
UIView.animate(withDuration: UI.duration(transitionDuration)) {
viewController.view.layer.opacity = 1
}
}
/// Hides the loading overlay view from showing over the specified view controller
///
/// - Parameter parentViewController: The parent view controller that the overlay is shown above.
///
static func hide(from parentViewController: UIViewController) {
guard let view = parentViewController.view.window?.viewWithTag(overlayViewTag) else { return }
UIView.animate(withDuration: UI.duration(transitionDuration)) {
view.layer.opacity = 0
} completion: { _ in
view.removeFromSuperview()
}
}
}

View File

@ -1,47 +0,0 @@
import SwiftUI
import XCTest
@testable import BitwardenShared
class LoadingOverlayDisplayHelperTests: BitwardenTestCase {
/// `show(in:state:)` shows the loading overlay in the parent view controller.
func test_show() throws {
let parentViewController = UIViewController()
let window = UIWindow()
window.rootViewController = parentViewController
window.makeKeyAndVisible()
LoadingOverlayDisplayHelper.show(
in: parentViewController,
state: LoadingOverlayState(title: "Loading..."),
)
let overlayView = try XCTUnwrap(window.viewWithTag(LoadingOverlayDisplayHelper.overlayViewTag))
XCTAssertEqual(overlayView.layer.opacity, 1)
guard #unavailable(iOS 26) else {
return
}
XCTAssertNil(overlayView.layer.backgroundColor)
}
/// `hide(from:)` hides the loading overlay in the parent view controller.
func test_hide() throws {
let parentViewController = UIViewController()
let window = UIWindow()
window.rootViewController = parentViewController
window.makeKeyAndVisible()
LoadingOverlayDisplayHelper.show(
in: parentViewController,
state: LoadingOverlayState(title: "Loading..."),
)
let overlayView = try XCTUnwrap(window.viewWithTag(LoadingOverlayDisplayHelper.overlayViewTag))
LoadingOverlayDisplayHelper.hide(from: parentViewController)
waitFor { overlayView.superview == nil }
XCTAssertNil(overlayView.superview)
XCTAssertEqual(overlayView.layer.opacity, 0)
}
}

View File

@ -1,8 +0,0 @@
/// State used to configure the display of a `LoadingOverlayView`.
///
public struct LoadingOverlayState: Equatable {
// MARK: Properties
/// The title of the loading overlay, displayed below the activity indicator.
let title: String
}

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenKitMocks
import BitwardenResources
import SwiftUI

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenKitMocks
import BitwardenResources
import SwiftUI

View File

@ -1,3 +1,4 @@
import BitwardenKit
import SwiftUI
import XCTest

View File

@ -1,4 +1,5 @@
import AVFoundation
import BitwardenKit
import BitwardenKitMocks
import SwiftUI
import XCTest

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
// MARK: - EditCollectionsProcessorDelegate

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenKitMocks
import BitwardenResources
import BitwardenSdk

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import BitwardenSdk

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenKitMocks
import BitwardenResources
import BitwardenSdk

View File

@ -1,3 +1,4 @@
import BitwardenKit
import XCTest
@testable import AuthenticatorShared