├── .github
├── FUNDING.yml
└── workflows
│ └── build.yaml
├── .gitignore
├── .gitmodules
├── .spi.yaml
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
└── Diligence
│ ├── Builders
│ ├── AcknowledgementsBuilder.swift
│ ├── ActionsBuilder.swift
│ ├── CreditsBuilder.swift
│ ├── LicenseGroupsBuilder.swift
│ └── LicensesBuilder.swift
│ ├── Commands
│ ├── AboutCommands.swift
│ └── ActionsCommands.swift
│ ├── Environment
│ └── PrefersTextualRepresentationEnvironmentKey.swift
│ ├── Extensions
│ ├── Array.swift
│ ├── Bundle.swift
│ ├── Color.swift
│ ├── Licensable.swift
│ ├── NSWindow.swift
│ ├── String.swift
│ ├── UIView.swift
│ ├── URL.swift
│ └── View.swift
│ ├── Model
│ ├── Acknowledgements.swift
│ ├── Action.swift
│ ├── Contents.swift
│ ├── Credit.swift
│ ├── License.swift
│ ├── LicenseGroup.swift
│ └── NamedURL.swift
│ ├── Resources
│ └── LICENSE
│ ├── Scenes
│ ├── AboutWindowGroup.swift
│ ├── MacAboutWindow.swift
│ └── MacLicenseWindowGroup.swift
│ ├── Styles
│ ├── AttributeLabeledContentStyle.swift
│ └── TableViewRow.swift
│ ├── Utilities
│ └── NativeFont.swift
│ ├── View Modifiers
│ ├── HookView.swift
│ ├── HorizontalSpace.swift
│ ├── Hyperlink.swift
│ ├── IsVisibleInScrollView.swift
│ ├── PaddingWithMultiplier.swift
│ └── UnderlinesForDifferentiation.swift
│ ├── Views
│ ├── AboutButton.swift
│ ├── AboutLink.swift
│ ├── AboutView.swift
│ ├── ActionSection.swift
│ ├── ApplicationNameTitle.swift
│ ├── BuildSection.swift
│ ├── CreditSection.swift
│ ├── DefaultAboutLinkLabel.swift
│ ├── HeaderSection.swift
│ ├── Icon.swift
│ ├── LabeledContent.swift
│ ├── LicenseRow.swift
│ ├── LicenseSection.swift
│ ├── LicenseView.swift
│ ├── Link.swift
│ ├── MacAboutSection.swift
│ ├── MacAboutView.swift
│ ├── MacIcon.swift
│ ├── MacLicenseView.swift
│ ├── NonWrappingText.swift
│ └── PhoneAboutView.swift
│ └── Windows
│ └── NSLicenseWindow.swift
├── Tests
└── DiligenceTests
│ ├── DiligenceTests.swift
│ └── Equivalence.swift
├── images
├── Bookmarks-macOS.png
├── Fileaway-macOS.png
├── anytime-iOS.png
├── opl-iOS.png
└── statuspanel-iOS.png
└── scripts
├── build-number.swift
├── build.sh
├── environment.sh
├── install-dependencies.sh
└── release.sh
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [jbmorley]
2 |
--------------------------------------------------------------------------------
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | pull_request:
5 | branches: [ main ]
6 | push:
7 | branches: [ main ]
8 | schedule:
9 | - cron: '0 9 * * *'
10 | workflow_dispatch:
11 |
12 | jobs:
13 | test:
14 |
15 | name: build
16 | runs-on: macos-ventura
17 |
18 | steps:
19 |
20 | - name: Checkout repository
21 | uses: actions/checkout@v4
22 | with:
23 | submodules: recursive
24 | fetch-depth: 0
25 |
26 | - name: Install dependencies
27 | run: scripts/install-dependencies.sh
28 |
29 | - name: Build and test
30 | run: exec ./scripts/build.sh
31 |
32 | - name: Release
33 | if: github.ref == 'refs/heads/main'
34 | env:
35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
36 | run: scripts/release.sh
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.build
2 | /.swiftpm
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "scripts/changes"]
2 | path = scripts/changes
3 | url = https://github.com/jbmorley/changes.git
4 |
--------------------------------------------------------------------------------
/.spi.yaml:
--------------------------------------------------------------------------------
1 | version: 1
2 | builder:
3 | configs:
4 | - platform: ios
5 | documentation_targets: [Diligence]
6 | - platform: macos
7 | documentation_targets: [Diligence]
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018-2025 Jason Morley
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "Licensable",
6 | "repositoryURL": "https://github.com/inseven/licensable",
7 | "state": {
8 | "branch": null,
9 | "revision": "e70ba15aad836c880024ec6e9f6f09e472fac956",
10 | "version": "0.0.9"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.5
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "Diligence",
7 | platforms: [
8 | .iOS(.v15),
9 | .macOS(.v12)
10 | ],
11 | products: [
12 | .library(
13 | name: "Diligence",
14 | targets: ["Diligence"]),
15 | ],
16 | dependencies: [
17 | .package(url: "https://github.com/inseven/licensable", from: "0.0.10"),
18 | ],
19 | targets: [
20 | .target(
21 | name: "Diligence",
22 | dependencies: [
23 | .product(name: "Licensable", package: "licensable"),
24 | ],
25 | resources: [.process("Resources")]),
26 | .testTarget(
27 | name: "DiligenceTests",
28 | dependencies: ["Diligence"]),
29 | ]
30 | )
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Diligence
2 |
3 | [](https://github.com/inseven/diligence/actions/workflows/build.yaml)
4 |
5 | SwiftUI About Screens
6 |
7 | ## Overview
8 |
9 | Diligence is a lightweight Swift package with a collection UI controls for building about screens for macOS and iOS apps.
10 |
11 | ## Screenshots
12 |
13 | ### iOS
14 |
15 | | Anytime | StatusPanel | OPL |
16 | | ---------------------------------- | ------------------------------------------ | -------------------------- |
17 | |  |  |  |
18 |
19 | ### macOS
20 |
21 |
22 | ## Build Numbers
23 |
24 | Diligence supports build numbers conforming to the following structure:
25 |
26 | ```
27 | YYmmddHHMMxxxxxxxx
28 | ```
29 |
30 | - `YY` -- two-digit year
31 | - `mm` -- month
32 | - `dd` -- day
33 | - `HH` -- hours (24h)
34 | - `MM` -- minutes
35 | - `xxxxxxxx` -- zero-padded integer representation of a 6-character commit SHA
36 |
37 | These build numbers are guaranteed to be always incrementing and, as such, safe to be used for iOS and macOS apps while also encoding the build date and commit.
38 |
39 | If Diligence detects a build number in this format, it will display this additional information in the about screen.
40 |
41 | #### Generating Build Numbers
42 |
43 | Diligence comes with a Swift command-line script that can be used to generate suitable build numbers. From the root Diligence directory, run the following command:
44 |
45 | ```bash
46 | scripts/build-number.swift
47 | 221021001716408432
48 | ```
49 |
50 | This can be injected into your project build by building from the command line and setting the `CURRENT_PROJECT_VERSION` environment variable. For example, the command line to archive a release build for the TinyBoard project is as follows:
51 |
52 | ```bash
53 | BUILD_NUMBER=`diligence/scripts/build-number.swift`
54 | xcode_project \
55 | -scheme "TinyBoard" \
56 | -config Release \
57 | CURRENT_PROJECT_VERSION=$BUILD_NUMBER \
58 | archive
59 | ```
60 |
61 | ## Usage
62 |
63 | ### iOS
64 |
65 | ```swift
66 | AboutView {
67 | Action("InSeven Limited", url: URL(string: "https://inseven.co.uk")!)
68 | Action("Privacy Policy", url: URL(string: "https://anytime.world/privacy-policy")!)
69 | Action("Support", url: URL(address: "support@anytime.world", subject: "Anytime Support")!)
70 | } acknowledgements: {
71 | Acknowledgements("Contributors") {
72 | Credit("Jason Morley", url: URL(string: "https://jbmorley.co.uk"))
73 | Credit("Pavlos Vinieratos", url: URL(string: "https://github.com/pvinis"))
74 | Credit("Sarah Barbour")
75 | }
76 | Acknowledgements("Graphics") {
77 | Credit("Anna Wilk")
78 | }
79 | Acknowledgements("Thanks") {
80 | Credit("Blake Merryman")
81 | Credit("Joanne Wong")
82 | Credit("Johannes Weiß")
83 | Credit("Lukas Fittl")
84 | Credit("Michael Dales")
85 | Credit("Michi Spevacek")
86 | Credit("Mike Rhodes")
87 | Credit("Sara Frederixon")
88 | Credit("Terrence Talbot")
89 | Credit("Tom Sutcliffe")
90 | }
91 | } licenses: {
92 | License("Introspect", author: "Timber Software", filename: "introspect-license")
93 | }
94 | ```
95 |
96 | ### macOS
97 |
98 | ```swift
99 | import SwiftUI
100 |
101 | import Diligence
102 |
103 | @main
104 | struct BookmarksApp: App {
105 |
106 | var body: some Scene {
107 | WindowGroup {
108 | ContentView()
109 | }
110 |
111 | About {
112 | Action("InSeven Limited", url: URL(string: "https://inseven.co.uk")!)
113 | Action("Support", url: URL(address: "support@inseven.co.uk", subject: "Bookmarks Support")!)
114 | } acknowledgements: {
115 | Acknowledgements("Developers") {
116 | Credit("Jason Morley", url: URL(string: "https://jbmorley.co.uk"))
117 | }
118 | Acknowledgements("Thanks") {
119 | Credit("Blake Merryman")
120 | Credit("Joanne Wong")
121 | Credit("Lukas Fittl")
122 | Credit("Pavlos Vinieratos")
123 | Credit("Sara Frederixon")
124 | Credit("Sarah Barbour")
125 | Credit("Terrence Talbot")
126 | }
127 | } licenses: {
128 | License("Binding+mappedToBool", author: "Joseph Duffy", filename: "Binding+mappedToBool")
129 | License("Diligence", author: "Jason Morley", filename: "Diligence")
130 | License("Interact", author: "Jason Morley", filename: "interact-license")
131 | License("Introspect", author: "Timber Software", filename: "Introspect")
132 | License("SQLite.swift", author: "Stephen Celis", filename: "SQLite-swift")
133 | License("TFHpple", author: "Topfunky Corporation", filename: "TFHpple")
134 | }
135 |
136 | }
137 | }
138 | ```
139 |
140 | ### Caveats
141 |
142 | Diligence currently makes some assumptions that will be addressed in future updates:
143 |
144 | - Diligence expects to find an image asset named "Icon" in your application's main bundle containing your icon.
145 |
--------------------------------------------------------------------------------
/Sources/Diligence/Builders/AcknowledgementsBuilder.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import Foundation
22 |
23 | public protocol AcknowledgementsConvertible {
24 | func asAcknowledgements() -> [Acknowledgements]
25 | }
26 |
27 | @resultBuilder public struct AcknowledgementsBuilder {
28 |
29 | public static func buildBlock() -> [Acknowledgements] {
30 | return []
31 | }
32 |
33 | public static func buildBlock(_ acknowledgements: Acknowledgements...) -> [Acknowledgements] {
34 | return acknowledgements
35 | }
36 |
37 | public static func buildBlock(_ values: AcknowledgementsConvertible...) -> [Acknowledgements] {
38 | return values
39 | .flatMap { $0.asAcknowledgements() }
40 | }
41 |
42 | }
43 |
44 | extension Array: AcknowledgementsConvertible where Element == Acknowledgements {
45 |
46 | public func asAcknowledgements() -> [Acknowledgements] {
47 | return self
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/Diligence/Builders/ActionsBuilder.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import Foundation
22 |
23 | public protocol ActionsConvertible {
24 | func asActions() -> [Action]
25 | }
26 |
27 | @resultBuilder public struct ActionsBuilder {
28 |
29 | public static func buildBlock() -> [Action] {
30 | return []
31 | }
32 |
33 | public static func buildBlock(_ actions: Action...) -> [Action] {
34 | return actions
35 | }
36 |
37 | public static func buildBlock(_ values: ActionsConvertible...) -> [Action] {
38 | return values
39 | .flatMap { $0.asActions() }
40 | }
41 |
42 | }
43 |
44 | extension Array: ActionsConvertible where Element == Action {
45 |
46 | public func asActions() -> [Action] {
47 | return self
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/Diligence/Builders/CreditsBuilder.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import Foundation
22 |
23 | @resultBuilder public struct CreditsBuilder {
24 |
25 | public static func buildBlock() -> [Credit] {
26 | return []
27 | }
28 |
29 | public static func buildBlock(_ credits: Credit...) -> [Credit] {
30 | return credits
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/Diligence/Builders/LicenseGroupsBuilder.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import Foundation
22 |
23 | public protocol LicenseGroupsConvertible {
24 | func asLicenseGroups() -> [LicenseGroup]
25 | }
26 |
27 | @resultBuilder public struct LicenseGroupsBuilder {
28 |
29 | public static func buildBlock() -> [LicenseGroup] {
30 | return []
31 | }
32 |
33 | public static func buildBlock(_ licenseGroup: LicenseGroup...) -> [LicenseGroup] {
34 | return licenseGroup
35 | }
36 |
37 | public static func buildBlock(_ values: LicenseGroupsConvertible...) -> [LicenseGroup] {
38 | return values
39 | .flatMap { $0.asLicenseGroups() }
40 | }
41 |
42 | }
43 |
44 | extension Array: LicenseGroupsConvertible where Element == LicenseGroup {
45 |
46 | public func asLicenseGroups() -> [LicenseGroup] {
47 | return self
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/Diligence/Builders/LicensesBuilder.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import Foundation
22 |
23 | import Licensable
24 |
25 | public protocol LicensesConvertible {
26 | func asLicenses() -> [Licensable]
27 | }
28 |
29 | @resultBuilder public struct LicensesBuilder {
30 |
31 | public static func buildBlock() -> [Licensable] {
32 | return []
33 | }
34 |
35 | public static func buildBlock(_ licenses: Licensable...) -> [Licensable] {
36 | return licenses
37 | }
38 |
39 | public static func buildBlock(_ values: LicensesConvertible...) -> [Licensable] {
40 | return values
41 | .flatMap { $0.asLicenses() }
42 | }
43 |
44 | public static func buildExpression(_ expression: Licensable) -> [Licensable] {
45 | return [expression]
46 | }
47 |
48 | public static func buildArray(_ components: [[Licensable]]) -> [Licensable] {
49 | return Array(components.joined())
50 | }
51 |
52 | }
53 |
54 | extension Array: LicensesConvertible where Element == Licensable {
55 |
56 | public func asLicenses() -> [Licensable] {
57 | return self
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/Sources/Diligence/Commands/AboutCommands.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | #if compiler(>=5.7) && os(macOS)
24 |
25 | @available(macOS 13, *)
26 | public struct AboutCommands: Commands {
27 |
28 | @Environment(\.openWindow) private var openWindow
29 |
30 | public init() {}
31 |
32 | public var body: some Commands {
33 |
34 | CommandGroup(replacing: .appInfo) {
35 | Button("About \(Bundle.main.preferredName ?? "")") {
36 | openWindow(id: MacAboutWindow.windowID)
37 | }
38 | }
39 |
40 | }
41 |
42 | }
43 |
44 | #endif
45 |
--------------------------------------------------------------------------------
/Sources/Diligence/Commands/ActionsCommands.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | @available(macOS 13, *, iOS 16, *)
24 | public struct ActionsCommands: Commands {
25 |
26 | public enum Placement {
27 | case before(CommandGroupPlacement)
28 | case after(CommandGroupPlacement)
29 | case replacing(CommandGroupPlacement)
30 | }
31 |
32 | @Environment(\.openURL) private var openURL
33 |
34 | let placement: Placement
35 | let actions: [Action]
36 |
37 | public init(placement: Placement = .replacing(.help), @ActionsBuilder actions: () -> [Action]) {
38 | self.placement = placement
39 | self.actions = actions()
40 | }
41 |
42 | public init(_ contents: Contents, placement: Placement = .replacing(.help)) {
43 | self.placement = placement
44 | self.actions = contents.actions
45 | }
46 |
47 | var buttons: some View {
48 | ForEach(actions) { action in
49 | Button {
50 | openURL(action.url)
51 | } label: {
52 | Text(action.title)
53 | }
54 | }
55 | }
56 |
57 | public var body: some Commands {
58 | switch placement {
59 | case .before(let before):
60 | CommandGroup(before: before) {
61 | buttons
62 | }
63 | case .after(let after):
64 | CommandGroup(after: after) {
65 | buttons
66 | }
67 | case .replacing(let replacing):
68 | CommandGroup(replacing: replacing) {
69 | buttons
70 | }
71 | }
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/Sources/Diligence/Environment/PrefersTextualRepresentationEnvironmentKey.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | struct PrefersTextualRepresentationEnvironmentKey: EnvironmentKey {
24 |
25 | public static var defaultValue: Bool = false
26 |
27 | }
28 |
29 | extension EnvironmentValues {
30 |
31 | public var prefersTextualRepresentation: Bool {
32 | get { self[PrefersTextualRepresentationEnvironmentKey.self] }
33 | set { self[PrefersTextualRepresentationEnvironmentKey.self] = newValue }
34 | }
35 |
36 | }
37 |
38 | extension View {
39 |
40 | func prefersTextualRepresentation(_ prefersTextualRepresentation: Bool = true) -> some View {
41 | return environment(\.prefersTextualRepresentation, prefersTextualRepresentation)
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/Diligence/Extensions/Array.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import Foundation
22 |
23 | import Licensable
24 |
25 | extension Array where Element == Licensable {
26 |
27 | /// Return an array ensuing the built-in Diligence license exists, and exists only once in the array.
28 | func includingDiligenceLicense() -> [Licensable] {
29 | return self + [.diligence]
30 | }
31 |
32 | /// Sort licenses alphabetically by name.
33 | func sortedByName() -> [Licensable] {
34 | return sorted { lhs, rhs in
35 | return lhs.name.localizedStandardCompare(rhs.name) == .orderedAscending
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/Diligence/Extensions/Bundle.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import Foundation
22 |
23 | extension Bundle {
24 |
25 | public var name: String? {
26 | return infoDictionary?["CFBundleName"] as? String
27 | }
28 |
29 | public var displayName: String? {
30 | return infoDictionary?["CFBundleDisplayName"] as? String
31 | }
32 |
33 | public var preferredName: String? {
34 | return displayName ?? name
35 | }
36 |
37 | public var version: String? {
38 | return infoDictionary?["CFBundleShortVersionString"] as? String
39 | }
40 |
41 | public var build: String? {
42 | return Bundle.main.infoDictionary?["CFBundleVersion"] as? String
43 | }
44 |
45 | public var extendedVersion: String? {
46 | var components: [String] = []
47 | if let version {
48 | components.append(version)
49 | }
50 | if let build {
51 | components.append(build)
52 | }
53 | guard !components.isEmpty else {
54 | return nil
55 | }
56 | return components.joined(separator: ".")
57 | }
58 |
59 | public var utcBuildDate: Date? {
60 | guard let build = build,
61 | build.count == 18
62 | else {
63 | return nil
64 | }
65 | let dateString = String(build.prefix(10))
66 | let inputDateFormatter = DateFormatter()
67 | inputDateFormatter.locale = Locale(identifier: "en_US_POSIX")
68 | inputDateFormatter.dateFormat = "yyMMddHHmm"
69 | inputDateFormatter.timeZone = TimeZone(abbreviation: "UTC")
70 | return inputDateFormatter.date(from: dateString)
71 | }
72 |
73 | public var commit: String? {
74 | guard let build = build,
75 | build.count == 18
76 | else {
77 | return nil
78 | }
79 | guard let shaValue = Int(String(build.suffix(8))) else {
80 | return nil
81 | }
82 | return String(format: "%06x", shaValue)
83 | }
84 |
85 | public func commitUrl(for project: String) -> URL? {
86 | guard let commit = commit else {
87 | return nil
88 | }
89 | return URL(string: "https://github.com")?
90 | .appendingPathComponent(project)
91 | .appendingPathComponent("commit")
92 | .appendingPathComponent(commit)
93 | }
94 |
95 | public var aboutWindowTitle: String {
96 | return "About \(Bundle.main.preferredName ?? "")"
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/Sources/Diligence/Extensions/Color.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | #if compiler(>=5.7) && os(macOS)
24 |
25 | @available(macOS 13, *)
26 | extension Color {
27 |
28 | static let textBackgroundColor = Color(nsColor: .textBackgroundColor)
29 |
30 | }
31 |
32 | #endif
33 |
--------------------------------------------------------------------------------
/Sources/Diligence/Extensions/Licensable.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import Foundation
22 |
23 | import Licensable
24 |
25 | extension Licensable where Self == License {
26 |
27 | public static var diligence: License {
28 | let licenseURL = Bundle.module.url(forResource: "LICENSE", withExtension: nil)!
29 | return License(id: "https://github.com/inseven/diligence",
30 | name: "Diligence",
31 | author: "Jason Morley",
32 | text: try! String(contentsOf: licenseURL),
33 | attributes: [
34 | .url(URL(string: "https://github.com/inseven/diligence")!, title: "GitHub"),
35 | ],
36 | licenses: [
37 | .licensable
38 | ])
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/Sources/Diligence/Extensions/NSWindow.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | #if os(macOS)
22 |
23 | import AppKit
24 | import SwiftUI
25 |
26 | import Licensable
27 |
28 | extension NSWindow {
29 |
30 | @available(macOS 13, *)
31 | public convenience init(repository: String? = nil,
32 | copyright: String? = nil,
33 | @ActionsBuilder actions: () -> [Action],
34 | @AcknowledgementsBuilder acknowledgements: () -> [Acknowledgements] = { [] },
35 | @LicensesBuilder licenses: () -> [Licensable] = { [] }) {
36 | let aboutView = AboutView(repository: repository,
37 | copyright: copyright,
38 | actions: actions,
39 | acknowledgements: acknowledgements,
40 | licenses: licenses,
41 | usesAppKit: true)
42 | self.init(contentViewController: NSHostingController(rootView: aboutView))
43 | self.title = Bundle.main.aboutWindowTitle
44 | self.styleMask.remove(.resizable)
45 | self.styleMask.remove(.miniaturizable)
46 | }
47 |
48 | @available(macOS 13, *)
49 | public convenience init(repository: String? = nil,
50 | copyright: String? = nil,
51 | @ActionsBuilder actions: () -> [Action],
52 | @AcknowledgementsBuilder acknowledgements: () -> [Acknowledgements] = { [] },
53 | @LicenseGroupsBuilder licenses: () -> [LicenseGroup] = { [] }) {
54 | let aboutView = AboutView(repository: repository,
55 | copyright: copyright,
56 | actions: actions,
57 | acknowledgements: acknowledgements,
58 | licenses: licenses,
59 | usesAppKit: true)
60 | self.init(contentViewController: NSHostingController(rootView: aboutView))
61 | self.title = Bundle.main.aboutWindowTitle
62 | self.styleMask.remove(.resizable)
63 | self.styleMask.remove(.miniaturizable)
64 | }
65 |
66 | @available(macOS 13, *)
67 | public convenience init(_ contents: Contents) {
68 | let aboutView = AboutView(repository: contents.repository,
69 | copyright: contents.copyright,
70 | actions: { contents.actions },
71 | acknowledgements: { contents.acknowledgements },
72 | licenses: { contents.licenseGroups },
73 | usesAppKit: true)
74 | self.init(contentViewController: NSHostingController(rootView: aboutView))
75 | self.title = Bundle.main.aboutWindowTitle
76 | self.styleMask.remove(.resizable)
77 | self.styleMask.remove(.miniaturizable)
78 | }
79 |
80 | }
81 |
82 | #endif
83 |
--------------------------------------------------------------------------------
/Sources/Diligence/Extensions/String.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | struct StringLayoutMetrics {
24 |
25 | let size: CGSize
26 | let fontSize: CGFloat
27 |
28 | }
29 |
30 | extension String {
31 |
32 | init?(contentsOfBundleFile filename: String, bundle: Bundle = Bundle.main) {
33 | guard let path = bundle.path(forResource: filename, ofType: nil) else {
34 | return nil
35 | }
36 | try? self.init(contentsOfFile: path)
37 | }
38 |
39 | func metrics(forFontSize fontSize: CGFloat) -> StringLayoutMetrics {
40 | let font = NativeFont.monospacedSystemFont(ofSize: fontSize, weight: .regular)
41 | let size = self.size(withAttributes: [.font : font])
42 | return StringLayoutMetrics(size: size, fontSize: fontSize)
43 | }
44 |
45 | func fontSize(thatFits width: CGFloat,
46 | preferredFontSize: CGFloat,
47 | minimumFontSize: CGFloat) -> StringLayoutMetrics {
48 | precondition(minimumFontSize > 0)
49 | precondition(minimumFontSize < width)
50 |
51 | // Check to see if the preferred font size fits; return it if it does.
52 | let idealSize = metrics(forFontSize: preferredFontSize)
53 | if idealSize.size.width <= width {
54 | return idealSize
55 | }
56 |
57 | // Calculate the scale and return the preferred size.
58 | let scale = width / idealSize.size.width
59 | let fontSize = max(preferredFontSize * scale, minimumFontSize)
60 | return metrics(forFontSize: fontSize)
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/Sources/Diligence/Extensions/UIView.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | #if os(iOS)
22 |
23 | import UIKit
24 |
25 | extension UIView {
26 |
27 | var scrollView: UIScrollView? {
28 | guard let scrollView = superview as? UIScrollView else {
29 | return superview?.scrollView
30 | }
31 | return scrollView
32 | }
33 |
34 | }
35 |
36 | #endif
37 |
--------------------------------------------------------------------------------
/Sources/Diligence/Extensions/URL.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import Foundation
22 |
23 | public extension URL {
24 |
25 | init?(address: String, subject: String? = nil) {
26 | var components = URLComponents()
27 | components.scheme = "mailto"
28 | components.path = address
29 | if let subject = subject {
30 | components.queryItems = [URLQueryItem(name: "subject", value: subject)]
31 | }
32 | guard let url = components.url else {
33 | return nil
34 | }
35 | self = url
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/Diligence/Extensions/View.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | extension View {
24 |
25 | func prefersMonospaced() -> some View {
26 | #if compiler(>=5.7)
27 | if #available(macOS 13.0, iOS 16.0, *) {
28 | return monospaced()
29 | } else {
30 | return self
31 | }
32 | #else
33 | return self
34 | #endif
35 | }
36 |
37 | func prefersLinkForegroundStyle() -> some View {
38 | #if compiler(>=5.7)
39 | if #available(macOS 14, iOS 17, *) {
40 | return foregroundStyle(.link)
41 | } else {
42 | return self
43 | }
44 | #else
45 | return self
46 | #endif
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/Diligence/Model/Acknowledgements.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | public struct Acknowledgements: Identifiable {
24 |
25 | public let id = UUID()
26 | public let title: String
27 | public let credits: [Credit]
28 |
29 | public init(_ title: String, credits: [Credit]) {
30 | self.title = title
31 | self.credits = credits
32 | }
33 |
34 | public init(_ title: String, credits: [String]) {
35 | self.title = title
36 | self.credits = credits.map { Credit($0) }
37 | }
38 |
39 | public init(_ title: String, @CreditsBuilder credits: () -> [Credit]) {
40 | self.title = title
41 | self.credits = credits()
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/Diligence/Model/Action.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import Foundation
22 |
23 | public struct Action: Identifiable {
24 |
25 | public let id = UUID()
26 | public let title: String
27 | public let url: URL
28 |
29 | public init(_ title: String, url: URL) {
30 | self.title = title
31 | self.url = url
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/Diligence/Model/Contents.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import Foundation
22 |
23 | import Licensable
24 |
25 | public struct Contents {
26 |
27 | let repository: String?
28 | let copyright: String?
29 | let actions: [Action]
30 | let acknowledgements: [Acknowledgements]
31 | let licenseGroups: [LicenseGroup]
32 |
33 | public init(repository: String? = nil,
34 | copyright: String? = nil,
35 | @ActionsBuilder actions: () -> [Action],
36 | @AcknowledgementsBuilder acknowledgements: () -> [Acknowledgements] = { [] },
37 | @LicenseGroupsBuilder licenses: () -> [LicenseGroup] = { [] }) {
38 | self.repository = repository
39 | self.copyright = copyright
40 | self.actions = actions()
41 | self.acknowledgements = acknowledgements()
42 | self.licenseGroups = licenses()
43 | }
44 |
45 | public init(repository: String? = nil,
46 | copyright: String? = nil,
47 | @ActionsBuilder actions: () -> [Action],
48 | @AcknowledgementsBuilder acknowledgements: () -> [Acknowledgements] = { [] },
49 | @LicensesBuilder licenses: () -> [Licensable] = { [] }) {
50 | self.init(repository: repository,
51 | copyright: copyright,
52 | actions: actions,
53 | acknowledgements: acknowledgements,
54 | licenses: { LicenseGroup("Licenses", includeDiligenceLicense: true, licenses: licenses()) })
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/Sources/Diligence/Model/Credit.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | public struct Credit: Identifiable {
24 |
25 | public var id: String {
26 | return name
27 | }
28 |
29 | public let name: String
30 | public let url: URL?
31 |
32 | public init(_ name: String, url: URL? = nil) {
33 | self.name = name
34 | self.url = url
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/Diligence/Model/License.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import Foundation
22 |
23 | import Licensable
24 |
25 | public struct License: Identifiable, Licensable {
26 |
27 | public var id: String
28 | public let name: String
29 | public let author: String
30 | public let text: String
31 | public let attributes: [Attribute]
32 | public let licenses: [Licensable]
33 |
34 | public init(id: String = UUID().uuidString,
35 | name: String,
36 | author: String,
37 | text: String,
38 | attributes: [Attribute] = [],
39 | licenses: [Licensable] = []) {
40 | self.id = id
41 | self.name = name
42 | self.author = author
43 | self.text = text
44 | self.attributes = attributes
45 | self.licenses = licenses
46 | }
47 |
48 | public init(id: String = UUID().uuidString,
49 | name: String,
50 | author: String,
51 | attributes: [NamedURL] = [],
52 | text: String,
53 | licenses: [Licensable] = []) {
54 | self.id = id
55 | self.name = name
56 | self.author = author
57 | self.attributes = attributes.map { namedURL in
58 | return .url(namedURL.url, title: namedURL.name)
59 | }
60 | self.text = text
61 | self.licenses = licenses
62 | }
63 |
64 | public init(_ name: String,
65 | author: String,
66 | attributes: [NamedURL] = [],
67 | text: String,
68 | licenses: [Licensable] = []) {
69 | self.init(name: name, author: author, attributes: attributes, text: text, licenses: licenses)
70 | }
71 |
72 | public init(id: String = UUID().uuidString,
73 | name: String,
74 | author: String,
75 | attributes: [NamedURL] = [],
76 | filename: String,
77 | bundle: Bundle = Bundle.main,
78 | licenses: [Licensable] = []) {
79 | self.id = id
80 | self.name = name
81 | self.author = author
82 | self.attributes = attributes.map { namedURL in
83 | return .url(namedURL.url, title: namedURL.name)
84 | }
85 | self.text = String(contentsOfBundleFile: filename, bundle: bundle)!
86 | self.licenses = licenses
87 | }
88 |
89 | public init(_ name: String,
90 | author: String,
91 | attributes: [NamedURL] = [],
92 | filename: String,
93 | bundle: Bundle = Bundle.main) {
94 | self.init(name: name, author: author, attributes: attributes, filename: filename, bundle: bundle)
95 | }
96 |
97 | public init(_ name: String, author: String, attributes: [NamedURL] = [], url: URL, licenses: [Licensable] = []) {
98 | self.id = UUID().uuidString
99 | self.name = name
100 | self.author = author
101 | self.attributes = attributes.map { namedURL in
102 | return .url(namedURL.url, title: namedURL.name)
103 | }
104 | self.text = try! String(contentsOf: url)
105 | self.licenses = licenses
106 | }
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/Sources/Diligence/Model/LicenseGroup.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | import Licensable
24 |
25 | public struct LicenseGroup: Identifiable, Equatable {
26 |
27 | public static func == (lhs: LicenseGroup, rhs: LicenseGroup) -> Bool {
28 | return lhs.id == rhs.id
29 | }
30 |
31 | public let id = UUID()
32 |
33 | let title: String
34 | let licenses: [AnyLicensable]
35 |
36 | public init(_ title: String, includeDiligenceLicense: Bool = false, licenses: [Licensable]) {
37 | self.title = title
38 | if includeDiligenceLicense {
39 | self.licenses = licenses.includingDiligenceLicense().flatten().sortedByName().eraseToAnyLicensable()
40 | } else {
41 | self.licenses = licenses.flatten().sortedByName().eraseToAnyLicensable()
42 | }
43 | }
44 |
45 | public init(_ title: String, includeDiligenceLicense: Bool = false, @LicensesBuilder licenses: () -> [Licensable]) {
46 | self.init(title, includeDiligenceLicense: includeDiligenceLicense, licenses: licenses())
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/Diligence/Model/NamedURL.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | public struct NamedURL: Identifiable, Equatable {
24 |
25 | public var id = UUID()
26 |
27 | let name: String
28 | let url: URL
29 |
30 | public init(_ name: String, url: URL) {
31 | self.name = name
32 | self.url = url
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/Diligence/Resources/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018-2025 Jason Morley
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Sources/Diligence/Scenes/AboutWindowGroup.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | import Licensable
24 |
25 | #if compiler(>=5.7) && os(macOS)
26 |
27 | @available(macOS 13, *)
28 | public typealias About = AboutWindowGroup
29 |
30 | @available(macOS 13, *)
31 | public struct AboutWindowGroup: Scene {
32 |
33 | private let repository: String?
34 | private let copyright: String?
35 | private let actions: [Action]
36 | private let acknowledgements: [Acknowledgements]
37 | private let licenseGroups: [LicenseGroup]
38 |
39 | public init(repository: String? = nil,
40 | copyright: String? = nil,
41 | @ActionsBuilder actions: () -> [Action],
42 | @AcknowledgementsBuilder acknowledgements: () -> [Acknowledgements] = { [] },
43 | @LicenseGroupsBuilder licenses: () -> [LicenseGroup] = { [] }) {
44 | self.repository = repository
45 | self.copyright = copyright
46 | self.actions = actions()
47 | self.acknowledgements = acknowledgements()
48 | self.licenseGroups = licenses()
49 | }
50 |
51 | public init(repository: String? = nil,
52 | copyright: String? = nil,
53 | @ActionsBuilder actions: () -> [Action],
54 | @AcknowledgementsBuilder acknowledgements: () -> [Acknowledgements] = { [] },
55 | @LicensesBuilder licenses: () -> [Licensable] = { [] }) {
56 | self.init(repository: repository,
57 | copyright: copyright,
58 | actions: actions,
59 | acknowledgements: acknowledgements,
60 | licenses: { LicenseGroup("Licenses", includeDiligenceLicense: true, licenses: licenses()) })
61 | }
62 |
63 | public init(_ contents: Contents) {
64 | self.repository = contents.repository
65 | self.copyright = contents.copyright
66 | self.actions = contents.actions
67 | self.acknowledgements = contents.acknowledgements
68 | self.licenseGroups = contents.licenseGroups
69 | }
70 |
71 | public var body: some Scene {
72 | MacAboutWindow(repository: repository,
73 | copyright: copyright,
74 | actions: actions,
75 | acknowledgements: acknowledgements,
76 | licenseGroups: licenseGroups)
77 | .commands {
78 | AboutCommands()
79 | }
80 | MacLicenseWindowGroup(licenses: licenseGroups.flatMap { $0.licenses })
81 | }
82 |
83 | }
84 |
85 | #endif
86 |
--------------------------------------------------------------------------------
/Sources/Diligence/Scenes/MacAboutWindow.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | #if compiler(>=5.7) && os(macOS)
24 |
25 | @available(macOS 13, *)
26 | struct MacAboutWindow: Scene {
27 |
28 | static let windowID = "diligence-about-window"
29 |
30 | private let repository: String?
31 | private let copyright: String?
32 | private let actions: [Action]
33 | private let acknowledgements: [Acknowledgements]
34 | private let licenseGroups: [LicenseGroup]
35 |
36 | init(repository: String? = nil,
37 | copyright: String? = nil,
38 | actions: [Action],
39 | acknowledgements: [Acknowledgements],
40 | licenseGroups: [LicenseGroup]) {
41 | self.repository = repository
42 | self.copyright = copyright
43 | self.actions = actions
44 | self.acknowledgements = acknowledgements
45 | self.licenseGroups = licenseGroups
46 | }
47 |
48 | var body: some Scene {
49 | Window("About", id: Self.windowID) {
50 | MacAboutView(repository: repository,
51 | copyright: copyright,
52 | actions: actions,
53 | acknowledgements: acknowledgements,
54 | licenseGroups: licenseGroups)
55 | .navigationTitle(Bundle.main.aboutWindowTitle)
56 | }
57 | .windowResizability(.contentSize)
58 | .defaultPosition(.center)
59 | }
60 |
61 | }
62 |
63 | #endif
64 |
--------------------------------------------------------------------------------
/Sources/Diligence/Scenes/MacLicenseWindowGroup.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | import Licensable
24 |
25 | #if compiler(>=5.7) && os(macOS)
26 |
27 | @available(macOS 13, *)
28 | struct MacLicenseWindowGroup: Scene {
29 |
30 | struct LayoutMetrics {
31 | static let width = 500.0
32 | static let height = 600.0
33 | }
34 |
35 | static let windowID = "diligence-license-window"
36 |
37 | let licenses: [License.ID: Licensable]
38 |
39 | init(licenses: [Licensable]) {
40 | self.licenses = licenses
41 | .includingDiligenceLicense()
42 | .flatten()
43 | .reduce(into: [License.ID: Licensable]()) { partialResult, licensable in
44 | partialResult[licensable.id] = licensable
45 | }
46 | }
47 |
48 | var body: some Scene {
49 | WindowGroup(id: Self.windowID, for: License.ID.self) { $licenseId in
50 | if let licenseId, let license = licenses[licenseId] {
51 | MacLicenseView(license: license)
52 | }
53 | }
54 | .defaultSize(CGSize(width: LayoutMetrics.width, height: LayoutMetrics.height))
55 | .windowResizability(.contentSize)
56 | }
57 |
58 | }
59 |
60 | #endif
61 |
--------------------------------------------------------------------------------
/Sources/Diligence/Styles/AttributeLabeledContentStyle.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | #if compiler(>=5.7) && os(macOS)
24 |
25 | struct AttributeLabeledContentStyle: LabeledContentStyle {
26 |
27 | func makeBody(configuration: Configuration) -> some View {
28 | HStack {
29 | configuration.label
30 | Spacer()
31 | configuration.content
32 | .foregroundColor(.secondary)
33 | .textSelection(.enabled)
34 | }
35 | }
36 |
37 | }
38 |
39 | @available(macOS 13, *)
40 | extension LabeledContentStyle where Self == AttributeLabeledContentStyle {
41 |
42 | static var attribute: AttributeLabeledContentStyle {
43 | return AttributeLabeledContentStyle()
44 | }
45 |
46 | }
47 |
48 | #endif
49 |
--------------------------------------------------------------------------------
/Sources/Diligence/Styles/TableViewRow.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | #if os(iOS)
22 |
23 | import SwiftUI
24 |
25 | struct TableViewRowButtonStyle: ButtonStyle {
26 |
27 | struct LayoutMetrics {
28 | static let minHeight = 44.0
29 | static let horizontalPadding = 20.0
30 | static let cornerRadius = 10.0
31 | }
32 |
33 | func makeBody(configuration: Configuration) -> some View {
34 | configuration.label
35 | .foregroundColor(.accentColor)
36 | .frame(maxWidth: .infinity)
37 | .padding(.horizontal, LayoutMetrics.horizontalPadding)
38 | .frame(minHeight: LayoutMetrics.minHeight)
39 | .background(configuration.isPressed ? Color(uiColor: .systemGray4) : Color(uiColor: .secondarySystemGroupedBackground))
40 | .clipShape(RoundedRectangle(cornerRadius: LayoutMetrics.cornerRadius))
41 | }
42 |
43 | }
44 |
45 | extension ButtonStyle where Self == TableViewRowButtonStyle {
46 |
47 | static var tableViewRow: TableViewRowButtonStyle {
48 | return TableViewRowButtonStyle()
49 | }
50 |
51 | }
52 |
53 | #endif
54 |
--------------------------------------------------------------------------------
/Sources/Diligence/Utilities/NativeFont.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | #if os(macOS)
24 | typealias NativeFont = NSFont
25 | #else
26 | typealias NativeFont = UIFont
27 | #endif
28 |
--------------------------------------------------------------------------------
/Sources/Diligence/View Modifiers/HookView.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | #if os(iOS)
24 |
25 | struct HookView: UIViewRepresentable {
26 |
27 | let action: (UIView) -> Void?
28 |
29 | init(action: @escaping (UIView) -> Void?) {
30 | self.action = action
31 | }
32 |
33 | func makeUIView(context: Context) -> UIView {
34 | return UIView(frame: .zero)
35 | }
36 |
37 | func updateUIView(_ uiView: UIView, context: Context) {
38 | DispatchQueue.main.async {
39 | self.action(uiView)
40 | }
41 | }
42 | }
43 |
44 | extension View {
45 |
46 | func hookView(action: @escaping (UIView) -> Void) -> some View {
47 | return background(HookView(action: action))
48 | }
49 |
50 | }
51 |
52 | #endif
53 |
--------------------------------------------------------------------------------
/Sources/Diligence/View Modifiers/HorizontalSpace.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | struct HorizontalSpace: ViewModifier {
24 |
25 | public struct Edge: OptionSet {
26 | public let rawValue: UInt
27 |
28 | public static let leading = Edge(rawValue: 1 << 0)
29 | public static let trailing = Edge(rawValue: 1 << 1)
30 |
31 | public static let both: Edge = [.leading, .trailing]
32 |
33 | public init(rawValue: UInt) {
34 | self.rawValue = rawValue
35 | }
36 | }
37 |
38 | var edges: Edge = .both
39 |
40 | public init(_ edges: Edge) {
41 | self.edges = edges
42 | }
43 |
44 | public func body(content: Content) -> some View {
45 | HStack {
46 | if edges.contains(.leading) {
47 | Spacer()
48 | }
49 | content
50 | if edges.contains(.trailing) {
51 | Spacer()
52 | }
53 | }
54 | }
55 |
56 | }
57 |
58 | extension View {
59 |
60 | func horizontalSpace(_ edges: HorizontalSpace.Edge) -> some View {
61 | return modifier(HorizontalSpace(edges))
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/Sources/Diligence/View Modifiers/Hyperlink.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | struct Hyperlink: ViewModifier {
24 |
25 | @State var isHovering = false
26 |
27 | var action: () -> Void
28 |
29 | func body(content: Content) -> some View {
30 | content
31 | .textSelection(.disabled)
32 | .underlinesForDifferentiation()
33 | .foregroundColor(.accentColor)
34 | .onTapGesture(perform: action)
35 | .brightness(isHovering ? 0.2 : 0.0)
36 | #if os(macOS)
37 | .onHover { inside in
38 | isHovering = inside
39 | if inside {
40 | NSCursor.pointingHand.push()
41 | } else {
42 | NSCursor.pop()
43 | }
44 | }
45 | #endif
46 | }
47 |
48 | }
49 |
50 | extension View {
51 |
52 | func hyperlink(_ perform: @escaping () -> Void) -> some View {
53 | modifier(Hyperlink(action: perform))
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/Diligence/View Modifiers/IsVisibleInScrollView.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import Combine
22 | import SwiftUI
23 |
24 | #if os(iOS)
25 |
26 | struct IsVisibleInScrollView: ViewModifier {
27 |
28 | class ViewModel: ObservableObject {
29 | @Published var offset: CGFloat = 0
30 | var cancellable: AnyCancellable?
31 | }
32 |
33 | @StateObject var viewModel = ViewModel()
34 | @Binding var isVisible: Bool
35 | private let threshold: CGFloat
36 |
37 | init(isVisible: Binding, threshold: CGFloat) {
38 | _isVisible = isVisible
39 | self.threshold = threshold
40 | }
41 |
42 | func body(content: Content) -> some View {
43 | content
44 | .hookView { view in
45 | guard let scrollView = view.scrollView else {
46 | return
47 | }
48 | viewModel.cancellable = scrollView
49 | .publisher(for: \.contentOffset)
50 | .receive(on: DispatchQueue.main)
51 | .sink { contentOffset in
52 | let scrollOffsetY = scrollView.safeAreaInsets.top + contentOffset.y
53 | let relativeViewFrame = view.convert(view.frame, to: scrollView)
54 | let viewPositionY = relativeViewFrame.maxY - scrollOffsetY
55 | viewModel.offset = viewPositionY
56 | }
57 | }
58 | .onChange(of: viewModel.offset) { offset in
59 | let isVisible = offset > threshold
60 | guard self.isVisible != isVisible else {
61 | return
62 | }
63 | withAnimation {
64 | self.isVisible = isVisible
65 | }
66 | }
67 |
68 | }
69 |
70 | }
71 |
72 | extension View {
73 |
74 | func isVisibleInScrollView(_ isVisible: Binding, threshold: CGFloat = 0.0) -> some View {
75 | return modifier(IsVisibleInScrollView(isVisible: isVisible, threshold: threshold))
76 | }
77 |
78 | }
79 |
80 | #endif
81 |
--------------------------------------------------------------------------------
/Sources/Diligence/View Modifiers/PaddingWithMultiplier.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | struct PaddingWithMultiplier: ViewModifier {
24 |
25 | var edges: Edge.Set
26 | var multiplier: Int
27 |
28 | init(_ edges: Edge.Set, multiplier: Int) {
29 | self.edges = edges
30 | self.multiplier = multiplier
31 | }
32 |
33 | func body(content: Content) -> some View {
34 | if multiplier <= 1 {
35 | content
36 | .padding(edges)
37 | } else {
38 | content
39 | .modifier(PaddingWithMultiplier(edges, multiplier: multiplier - 1))
40 | .padding(edges)
41 | }
42 | }
43 |
44 | }
45 |
46 | extension View {
47 |
48 | func padding(_ edges: Edge.Set, multiplier: Int) -> some View {
49 | return modifier(PaddingWithMultiplier(edges, multiplier: multiplier))
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/Sources/Diligence/View Modifiers/UnderlinesForDifferentiation.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | private struct UnderlinesForDifferentiation: ViewModifier {
24 |
25 | @Environment(\.accessibilityDifferentiateWithoutColor) var accessibilityDifferentiateWithoutColor
26 |
27 | func body(content: Content) -> some View {
28 | #if compiler(>=5.7)
29 | if #available(macOS 13.0, iOS 16.0, *) {
30 | if accessibilityDifferentiateWithoutColor {
31 | content
32 | .underline()
33 | } else {
34 | content
35 | }
36 | } else {
37 | content
38 | }
39 | #else
40 | content
41 | #endif
42 | }
43 | }
44 |
45 | extension View {
46 |
47 | func underlinesForDifferentiation() -> some View {
48 | return modifier(UnderlinesForDifferentiation())
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/Diligence/Views/AboutButton.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | #if os(iOS)
24 |
25 | public struct AboutButton: View {
26 |
27 | enum SheetType: Identifiable {
28 |
29 | var id: Self {
30 | return self
31 | }
32 |
33 | case about
34 | }
35 |
36 | @Environment(\.dismiss) var dismiss
37 |
38 | let contents: Contents
39 |
40 | @State var sheet: SheetType?
41 |
42 | public init(_ contents: Contents) {
43 | self.contents = contents
44 | }
45 |
46 | public var body: some View {
47 | Button {
48 | sheet = .about
49 | } label: {
50 | Text("About \(Bundle.main.preferredName ?? "")...")
51 | .foregroundColor(.primary)
52 | }
53 | .sheet(item: $sheet) { sheet in
54 | switch sheet {
55 | case .about:
56 | AboutView(contents)
57 | }
58 | }
59 |
60 | }
61 |
62 | }
63 |
64 | #endif
65 |
--------------------------------------------------------------------------------
/Sources/Diligence/Views/AboutLink.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018-2025 Jason Morley
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SwiftUI
22 |
23 | #if compiler(>=5.7) && os(macOS)
24 |
25 | @available(macOS 13.0, *)
26 | @available(iOS, unavailable)
27 | @available(tvOS, unavailable)
28 | @available(watchOS, unavailable)
29 | @available(visionOS, unavailable)
30 | public struct AboutLink