├── .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 | [![build](https://github.com/inseven/diligence/actions/workflows/build.yaml/badge.svg)](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 | | ![anytime](images/anytime-iOS.png) | ![statuspanel](images/statuspanel-iOS.png) | ![opl](images/opl-iOS.png) | 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