Skip to content

Commit

Permalink
Add Dismiss Button and Reverse Label Style (#37)
Browse files Browse the repository at this point in the history
# Add Dismiss Button and Reverse Label Style

## ♻️ Current situation & Problem
This PR adds a `DismissButton` and a `reverse` label style as
generalized components to SpeziViews.

## ⚙️ Release Notes 
* Add new `DismissButton` that displays as a small, circular "X" button.
* Add new `reverse` label style that reverses the label orientation
compared to the `titleAndIcon` style.

## 📚 Documentation
Documentation catalog was updated and adjusted.

## ✅ Testing
These are primarily visual components. Testing was done through snapshot
testing.


## 📝 Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md).
  • Loading branch information
Supereg authored Jun 21, 2024
1 parent 4d2a724 commit 0899c34
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 9 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,5 @@ jobs:
uses: StanfordSpezi/.github/.github/workflows/create-and-upload-coverage-report.yml@v2
with:
coveragereports: SpeziViews-iOS.xcresult SpeziViews-watchOS.xcresult SpeziViews-visionOS.xcresult SpeziViews-tvOS.xcresult TestApp-iOS.xcresult TestApp-iPad.xcresult TestApp-visionOS.xcresult
secrets:
token: ${{ secrets.CODECOV_TOKEN }}
6 changes: 6 additions & 0 deletions Sources/SpeziViews/SpeziViews.docc/SpeziViews.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Automatically adapt your view layouts to dynamic type sizes, device orientation,
- ``AsyncButton``
- ``SwiftUI/EnvironmentValues/processingDebounceDuration``
- ``CanvasView``
- ``DismissButton``

### Displaying Text

Expand All @@ -73,6 +74,11 @@ Automatically adapt your view layouts to dynamic type sizes, device orientation,
- ``SwiftUI/View/focusOnTap()``
- ``SwiftUI/View/observeOrientationChanges(_:)``

### Styles

- ``ReverseLabelStyle``
- ``SwiftUI/LabelStyle/reverse``

### Localization

- ``Foundation/LocalizedStringResource/BundleDescription/atURL(from:)``
Expand Down
48 changes: 48 additions & 0 deletions Sources/SpeziViews/Styles/ReverseLabelStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import SwiftUI


/// A label style that shows the title and icon in reverse layout compared to the standard `titleAndIcon` label style.
public struct ReverseLabelStyle: LabelStyle {
public func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.title
configuration.icon
}
.accessibilityElement(children: .combine)
}
}


extension LabelStyle where Self == ReverseLabelStyle {
/// A label style that shows the title and icon in reverse layout compared to the standard `titleAndIcon` label style.
public static var reverse: ReverseLabelStyle {
ReverseLabelStyle()
}
}


#if DEBUG
#Preview {
VStack {
SwiftUI.Label {
Text(verbatim: "75 %")
} icon: {
Image(systemName: "battery.100")
}
SwiftUI.Label {
Text(verbatim: "75 %")
} icon: {
Image(systemName: "battery.100")
}
.labelStyle(.reverse)
}
}
#endif
75 changes: 75 additions & 0 deletions Sources/SpeziViews/Views/Button/DismissButton.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import SwiftUI


/// Circular Dismiss button.
public struct DismissButton: View {
@Environment(\.dismiss) private var dismiss

public var body: some View {
#if os(visionOS) || os(tvOS) || os(macOS)
Button("Dismiss", systemImage: "xmark") {
dismiss()
}
#else
Button {
dismiss()
} label: {
Image(systemName: "xmark")
.font(.system(size: 10, weight: .bold, design: .rounded))
#if !os(watchOS)
.foregroundStyle(.secondary)
#endif
.background {
Circle()
#if os(iOS)
.fill(Color(uiColor: .secondarySystemBackground))
#elseif os(watchOS)
.fill(Color(uiColor: .darkGray))
#endif
.frame(width: 25, height: 25)
}
.frame(width: 27, height: 27) // make the tap-able button region slightly larger
}
.accessibilityLabel("Dismiss")
.buttonStyle(.plain)
#endif
}

public init() {}
}


#if DEBUG
#Preview {
#if os(macOS) // cannot preview sheets in macOS
NavigationStack {
Text(verbatim: "Hello World")
.toolbar {
DismissButton()
}
}
.frame(width: 500, height: 350)
#else
Text(verbatim: "")
.sheet(isPresented: .constant(true)) {
NavigationStack {
Text(verbatim: "Hello World")
.toolbar {
DismissButton()
}
}
.frame(width: 200, height: 200)
.presentationDetents([.medium])
.presentationCornerRadius(25)
}
#endif
}
#endif
19 changes: 19 additions & 0 deletions Tests/SpeziViewsTests/SnapshotTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,25 @@ final class SnapshotTests: XCTestCase {

assertSnapshot(of: largeRow, as: .image(layout: .device(config: .iPhone13Pro)), named: "iphone-XA3")
assertSnapshot(of: largeRow, as: .image(layout: .device(config: .iPadPro11)), named: "ipad-XA3")
#endif
}

func testReverseLabelStyle() {
let label = SwiftUI.Label("100 %", image: "battery.100")
.labelStyle(.reverse)

#if os(iOS)
assertSnapshot(of: label, as: .image(layout: .device(config: .iPhone13Pro)), named: "iphone-regular")
assertSnapshot(of: label, as: .image(layout: .device(config: .iPadPro11)), named: "ipad-regular")
#endif
}

func testDismissButton() {
let dismissButton = DismissButton()

#if os(iOS)
assertSnapshot(of: dismissButton, as: .image(layout: .device(config: .iPhone13Pro)), named: "iphone-regular")
assertSnapshot(of: dismissButton, as: .image(layout: .device(config: .iPadPro11)), named: "ipad-regular")
#endif
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 8 additions & 9 deletions Tests/UITests/TestAppUITests/SpeziViews/ViewsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,12 @@ final class ViewsTests: XCTestCase {

let app = XCUIApplication()

#if os(visionOS)
let paletteToolPencil = "palette_tool_pencil_band"
#else
let paletteToolPencil = "palette_tool_pencil_base"
#endif


XCTAssert(app.collectionViews.buttons["Canvas"].waitForExistence(timeout: 2))
app.collectionViews.buttons["Canvas"].tap()

XCTAssert(app.staticTexts["Did Draw Anything: false"].waitForExistence(timeout: 2))
XCTAssertFalse(app.images[paletteToolPencil].waitForExistence(timeout: 2))
XCTAssertFalse(app.images["palette_tool_pencil_base"].waitForExistence(timeout: 2))

let canvasView = app.scrollViews.firstMatch
canvasView.swipeRight()
Expand All @@ -55,7 +49,12 @@ final class ViewsTests: XCTestCase {
XCTAssert(app.buttons["Show Tool Picker"].waitForExistence(timeout: 2))
app.buttons["Show Tool Picker"].tap()

XCTAssert(app.images[paletteToolPencil].waitForExistence(timeout: 10))
#if os(visionOS)
// visionOS doesn't have the image anymore, this should be enough to check
XCTAssert(app.scrollViews.otherElements["Pen, black"].waitForExistence(timeout: 2.0))
#else
XCTAssert(app.images["palette_tool_pencil_base"].waitForExistence(timeout: 10))
#endif
canvasView.swipeLeft()

sleep(1)
Expand All @@ -66,7 +65,7 @@ final class ViewsTests: XCTestCase {
#endif

sleep(15) // waitForExistence will otherwise return immediately

Check warning on line 67 in Tests/UITests/TestAppUITests/SpeziViews/ViewsTests.swift

View workflow job for this annotation

GitHub Actions / Build and Test UI Tests visionOS / Test using xcodebuild or run fastlane

code after 'return' will never be executed
XCTAssertFalse(app.images[paletteToolPencil].waitForExistence(timeout: 10))
XCTAssertFalse(app.images["palette_tool_pencil_base"].waitForExistence(timeout: 10))
canvasView.swipeUp()
}

Expand Down

0 comments on commit 0899c34

Please sign in to comment.