diff --git a/SBBDesignSystemMobileSwiftUI/Views/SBBListItem.swift b/SBBDesignSystemMobileSwiftUI/Views/SBBListItem.swift index fbc5eb897..a196ec714 100644 --- a/SBBDesignSystemMobileSwiftUI/Views/SBBListItem.swift +++ b/SBBDesignSystemMobileSwiftUI/Views/SBBListItem.swift @@ -29,6 +29,7 @@ public struct SBBListItem: View { private let footnote: Text? private let footnoteAccessibility: Text? private let showBottomLine: Bool + private let rightImageIsCircled: Bool var leftSwipeButtonLabel: AnyView? var leftSwipeButtonAction: (() -> ())? @@ -52,8 +53,9 @@ public struct SBBListItem: View { - footnote: An optional label displayed underneath the main label. - footnoteAccessibility: The optional alternative text for the footnote's VoiceOver. - showBottomLine: Shows or hides a separator line at the bottom of the View (typically only false for last elements in a List). + - rightImageIsCircled: The right icon is in a circle. */ - public init(label: Text, labelAccessibility: Text? = nil, leftImage: Image? = nil, rightImage: Image? = Image(sbbIcon: .chevron_small_right_small), footnote: Text? = nil, footnoteAccessibility: Text? = nil, showBottomLine: Bool = true) { + public init(label: Text, labelAccessibility: Text? = nil, leftImage: Image? = nil, rightImage: Image? = Image(sbbIcon: .chevron_small_right_small), footnote: Text? = nil, footnoteAccessibility: Text? = nil, showBottomLine: Bool = true, rightImageIsCircled: Bool = true) { self.label = label self.labelAccessibility = labelAccessibility self.leftImage = leftImage @@ -61,6 +63,7 @@ public struct SBBListItem: View { self.footnote = footnote self.footnoteAccessibility = footnoteAccessibility self.showBottomLine = showBottomLine + self.rightImageIsCircled = rightImageIsCircled } /// SBBListItem Type. @@ -72,7 +75,7 @@ public struct SBBListItem: View { } @available(*, deprecated, message: "image renamed to leftImage. SBBListItemType removed, instead use rightImage to specify the desired image.") - public init(label: Text, labelAccessibility: Text? = nil, image: Image, footnote: Text? = nil, footnoteAccessibility: Text? = nil, type: SBBListItemType = .normal, showBottomLine: Bool = true) { + public init(label: Text, labelAccessibility: Text? = nil, image: Image, footnote: Text? = nil, footnoteAccessibility: Text? = nil, type: SBBListItemType = .normal, showBottomLine: Bool = true, rightImageIsCircled: Bool = true) { self.label = label self.labelAccessibility = labelAccessibility self.leftImage = image @@ -80,10 +83,11 @@ public struct SBBListItem: View { self.footnoteAccessibility = footnoteAccessibility self.rightImage = Image(sbbIcon: (type == .normal ? .chevron_small_right_small : .circle_information_small_small)) self.showBottomLine = showBottomLine + self.rightImageIsCircled = rightImageIsCircled } @available(*, deprecated, message: "image renamed to leftImage. SBBListItemType removed, instead use rightImage to specify the desired image.") - public init(label: Text, labelAccessibility: Text? = nil, footnote: Text? = nil, footnoteAccessibility: Text? = nil, type: SBBListItemType, showBottomLine: Bool = true) { + public init(label: Text, labelAccessibility: Text? = nil, footnote: Text? = nil, footnoteAccessibility: Text? = nil, type: SBBListItemType, showBottomLine: Bool = true, rightImageIsCircled: Bool = true) { self.label = label self.labelAccessibility = labelAccessibility self.leftImage = nil @@ -91,6 +95,7 @@ public struct SBBListItem: View { self.footnoteAccessibility = footnoteAccessibility self.rightImage = Image(sbbIcon: (type == .normal ? .chevron_small_right_small : .circle_information_small_small)) self.showBottomLine = showBottomLine + self.rightImageIsCircled = rightImageIsCircled } public var body: some View { @@ -160,12 +165,19 @@ public struct SBBListItem: View { .padding(.vertical, 12) Spacer() if let rightImage { - rightImage - .accessibility(hidden: true) - .frame(width: 32, height: 32) - .clipShape(Circle()) - .overlay(Circle().stroke(Color.sbbColor(.border))) - .padding(.vertical, 6) + if rightImageIsCircled { + rightImage + .accessibility(hidden: true) + .frame(width: 32, height: 32) + .clipShape(Circle()) + .overlay(Circle().stroke(Color.sbbColor(.border))) + .padding(.vertical, 6) + } else { + rightImage + .accessibility(hidden: true) + .frame(width: 32, height: 32) + .padding(.vertical, 6) + } } } .frame(minHeight: 44) diff --git a/SBBDesignSystemMobileSwiftUI/Views/SBBLoadingIndicator.swift b/SBBDesignSystemMobileSwiftUI/Views/SBBLoadingIndicator.swift index 76f4a834d..6da45daf6 100644 --- a/SBBDesignSystemMobileSwiftUI/Views/SBBLoadingIndicator.swift +++ b/SBBDesignSystemMobileSwiftUI/Views/SBBLoadingIndicator.swift @@ -24,6 +24,8 @@ public struct SBBLoadingIndicator: View { case normal /// Small SBBLoadingIndicator Size. case small + /// Tiny SBBLoadingIndicator Size. + case tiny var size: CGSize { switch self { @@ -31,6 +33,8 @@ public struct SBBLoadingIndicator: View { return CGSize(width: 55, height: 32) case .small: return CGSize(width: 31, height: 18) + case .tiny: + return CGSize(width: 15, height: 9) } } } @@ -90,7 +94,7 @@ public struct SBBLoadingIndicator: View { let innerWidth = width / CGFloat(cos(rotationInDegrees * .pi / 180)) * 3.0 self.innerWidth = innerWidth - let paddingBetweenRectangles = size == .normal ? 8.0 : 4.0 + let paddingBetweenRectangles = size == .normal ? 8.0 : size == .small ? 4.0 : 2.0 self.rectangleWidth = innerWidth / CGFloat(numberOfRectangles) + paddingBetweenRectangles rectangleOffsets = [-rectangleWidth, 0, rectangleWidth, 2.0*rectangleWidth, 3.0*rectangleWidth, 4.0*rectangleWidth] @@ -99,37 +103,34 @@ public struct SBBLoadingIndicator: View { } public var body: some View { - Group { - ZStack { - ForEach(rectangleProperties.indices, id:\.self) { index in - Rectangle() - .fill(style.color(for: colorScheme)) - .frame(width: innerWidth / CGFloat(numberOfRectangles), height: height) - .opacity(rectangleProperties[index].opacity) - .offset(x: rectangleProperties[index].offset) - .onAppear { - DispatchQueue.main.async { - withAnimation(Animation.linear(duration: animationDuration) - .repeatForever(autoreverses: false) - ) - { - rectangleProperties[index].offset = rectangleOffsets[index] - } - - withAnimation(Animation.linear(duration: animationDuration) - .repeatForever(autoreverses: false) - ) { - rectangleProperties[index].opacity = rectangleOpacity[index] - } + ZStack { + ForEach(rectangleProperties.indices, id:\.self) { index in + Rectangle() + .fill(style.color(for: colorScheme)) + .frame(width: innerWidth / CGFloat(numberOfRectangles), height: height) + .opacity(rectangleProperties[index].opacity) + .offset(x: rectangleProperties[index].offset) + .onAppear { + DispatchQueue.main.async { + withAnimation(Animation.linear(duration: animationDuration) + .repeatForever(autoreverses: false) + ) + { + rectangleProperties[index].offset = rectangleOffsets[index] + } + + withAnimation(Animation.linear(duration: animationDuration) + .repeatForever(autoreverses: false) + ) { + rectangleProperties[index].opacity = rectangleOpacity[index] } } - } + } } - .rotation3DEffect(.degrees(rotationInDegrees), axis: (x: 0, y: 1, z: 0), anchor: .leading) } - .frame(width: width, height: height, alignment: .leading) - .padding(16) - + .rotation3DEffect(.degrees(rotationInDegrees), axis: (x: 0, y: 1, z: 0), anchor: .leading) + .frame(width: width, height: height, alignment: .leading) + .padding(16) .accessibility(label: Text("Loading.".localized)) } } @@ -138,6 +139,15 @@ public struct SBBLoadingIndicator: View { struct SBBLoadingIndicator_Previews: PreviewProvider { static var previews: some View { Group { + VStack { + SBBLoadingIndicator() + .previewDisplayName("normal, red, light") + SBBLoadingIndicator(size: .small) + .previewDisplayName("normal, red, light") + SBBLoadingIndicator(size: .tiny) + .previewDisplayName("normal, red, light") + + } Group { SBBLoadingIndicator() .previewDisplayName("normal, red, light") @@ -170,6 +180,23 @@ struct SBBLoadingIndicator_Previews: PreviewProvider { .previewDisplayName("small, white, dark") .environment(\.colorScheme, .dark) } + + Group { + SBBLoadingIndicator(size: .tiny) + .previewDisplayName("tiny, red, light") + SBBLoadingIndicator(size: .tiny) + .previewDisplayName("tiny, red, dark") + .environment(\.colorScheme, .dark) + SBBLoadingIndicator(size: .tiny, style: .grey) + .previewDisplayName("tiny, grey, light") + SBBLoadingIndicator(size: .tiny, style: .grey) + .previewDisplayName("tiny, grey, dark") + SBBLoadingIndicator(size: .tiny, style: .primaryBackground) + .previewDisplayName("tiny, white, light") + SBBLoadingIndicator(size: .small, style: .primaryBackground) + .previewDisplayName("tiny, white, dark") + .environment(\.colorScheme, .dark) + } } .previewLayout(.sizeThatFits) } diff --git a/SBBDesignSystemMobileSwiftUIDemoTests/Views/SBBLoadingIndicatorTests.swift b/SBBDesignSystemMobileSwiftUIDemoTests/Views/SBBLoadingIndicatorTests.swift index 30e4eba25..cf143e944 100644 --- a/SBBDesignSystemMobileSwiftUIDemoTests/Views/SBBLoadingIndicatorTests.swift +++ b/SBBDesignSystemMobileSwiftUIDemoTests/Views/SBBLoadingIndicatorTests.swift @@ -9,32 +9,57 @@ import SBBDesignSystemMobileSwiftUI class SBBLoadingIndicatorTests: XCTestCase { + // WORKAROUND: no 3D rotation applied on standard snapshots but Documentation snapshots renders indicator correctly (e.g. image) -> renders image and compare that + private func renderSBBLoadingIndicator(view: any View, colorScheme: ColorScheme) -> UIViewController { + let controller = view.colorScheme(colorScheme).toVC() + let view = controller.view + + let targetSize = controller.view.intrinsicContentSize + view?.bounds = CGRect(origin: .zero, size: targetSize) + view?.backgroundColor = .clear + + let renderer = UIGraphicsImageRenderer(size: targetSize) + + let image = renderer.image { _ in + view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true) + } + + return Image(uiImage: image).toVC() + } + func testLoadingIndicatorNormalPrimary() { let view = SBBLoadingIndicator(size: .normal, style: .primary) for colorScheme in ColorScheme.allCases { view.recordDocumentationSnapshot(name: "SBBLoadingIndicator", colorScheme: colorScheme) - assertSnapshot(matching: view.colorScheme(colorScheme).toVC(), as: imagePortrait, record: recordNewReferenceSnapshots) + assertSnapshot(matching: renderSBBLoadingIndicator(view: view, colorScheme: colorScheme), as: imagePortrait, record: recordNewReferenceSnapshots) } } func testLoadingIndicatorNormalGrey() { let view = SBBLoadingIndicator(size: .normal, style: .grey) for colorScheme in ColorScheme.allCases { - assertSnapshot(matching: view.colorScheme(colorScheme).toVC(), as: imagePortrait, record: recordNewReferenceSnapshots) + assertSnapshot(matching: renderSBBLoadingIndicator(view: view, colorScheme: colorScheme), as: imagePortrait, record: recordNewReferenceSnapshots) } } func testLoadingIndicatorNormalWhite() { let view = SBBLoadingIndicator(size: .normal, style: .primaryBackground) for colorScheme in ColorScheme.allCases { - assertSnapshot(matching: view.colorScheme(colorScheme).toVC(), as: imagePortrait, record: recordNewReferenceSnapshots) + assertSnapshot(matching: renderSBBLoadingIndicator(view: view, colorScheme: colorScheme), as: imagePortrait, record: recordNewReferenceSnapshots) } } func testLoadingIndicatorSmallPrimary() { let view = SBBLoadingIndicator(size: .small, style: .primary) for colorScheme in ColorScheme.allCases { - assertSnapshot(matching: view.colorScheme(colorScheme).toVC(), as: imagePortrait, record: recordNewReferenceSnapshots) + assertSnapshot(matching: renderSBBLoadingIndicator(view: view, colorScheme: colorScheme), as: imagePortrait, record: recordNewReferenceSnapshots) + } + } + + func testLoadingIndicatorTineyPrimary() { + let view = SBBLoadingIndicator(size: .tiny, style: .primary) + for colorScheme in ColorScheme.allCases { + assertSnapshot(matching: renderSBBLoadingIndicator(view: view, colorScheme: colorScheme), as: imagePortrait, record: recordNewReferenceSnapshots) } } } diff --git a/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorNormalGrey.1.png b/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorNormalGrey.1.png index 62a048052..81f90c8f5 100644 Binary files a/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorNormalGrey.1.png and b/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorNormalGrey.1.png differ diff --git a/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorNormalGrey.2.png b/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorNormalGrey.2.png index 62a048052..81f90c8f5 100644 Binary files a/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorNormalGrey.2.png and b/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorNormalGrey.2.png differ diff --git a/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorNormalPrimary.1.png b/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorNormalPrimary.1.png index d68e44ace..a0ead7931 100644 Binary files a/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorNormalPrimary.1.png and b/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorNormalPrimary.1.png differ diff --git a/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorNormalPrimary.2.png b/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorNormalPrimary.2.png index d68e44ace..a0ead7931 100644 Binary files a/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorNormalPrimary.2.png and b/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorNormalPrimary.2.png differ diff --git a/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorSmallPrimary.1.png b/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorSmallPrimary.1.png index 8428070b9..ccb9d1561 100644 Binary files a/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorSmallPrimary.1.png and b/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorSmallPrimary.1.png differ diff --git a/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorSmallPrimary.2.png b/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorSmallPrimary.2.png index 8428070b9..ccb9d1561 100644 Binary files a/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorSmallPrimary.2.png and b/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorSmallPrimary.2.png differ diff --git a/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorTineyPrimary.1.png b/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorTineyPrimary.1.png new file mode 100644 index 000000000..cbdf66165 Binary files /dev/null and b/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorTineyPrimary.1.png differ diff --git a/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorTineyPrimary.2.png b/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorTineyPrimary.2.png new file mode 100644 index 000000000..cbdf66165 Binary files /dev/null and b/ci_scripts/__Snapshots__/Views/SBBLoadingIndicatorTests/testLoadingIndicatorTineyPrimary.2.png differ