From cf19568d7a198f0574a98f95d9f0eec7f82c8d04 Mon Sep 17 00:00:00 2001 From: Nikolai Madlener <33159293+NikolaiMadlener@users.noreply.github.com> Date: Wed, 18 Oct 2023 21:13:51 +0200 Subject: [PATCH] implement sheet listing all package dependencies from SPM; (#36) # *Add an Open-Source Contributions Screen* ## :recycle: Current situation & Problem The application currently does not provide any way to see all open-source tools used within the app, nor does it give access to all licenses used. See: [https://github.com/StanfordSpezi/SpeziTemplateApplication/issues/29](url) ## :gear: Release Notes - add a info button to the top right corner of Home view that opens the Contributions sheet - add Contributions sheet that lists all package dependencies used - package dependencies are read via SwiftPackageList API (using a build phase script, the view is always up do date with the latest dependencies). For more info check out the repo: [Swift-Package-List](https://github.com/FelixHerrmann/swift-package-list) - each dependency item in the list shows the package name, version/branch, a license label as well as a link to the GitHub repo - currently detected licenses (shown as tags in the UI) of packages are MIT, Apache-2.0, GPL-2.0, GPL-3.0. Could still be extended in the future. Here is how it looks like: ![Simulator Screenshot - iPhone 14 Pro - 2023-09-18 at 15 42 39](https://github.com/StanfordSpezi/SpeziTemplateApplication/assets/33159293/a22d225c-b70e-47c2-a6a5-dc75fcf3fa2e) ![Simulator Screenshot - iPhone 14 Pro - 2023-09-18 at 15 43 13](https://github.com/StanfordSpezi/SpeziTemplateApplication/assets/33159293/21918fcb-2646-4107-a04d-44ead92ced72) ## :books: Documentation TBA ## :white_check_mark: Testing - Simple UI test added that tests the info button and the subsequent existence of the contributions sheet ## :pencil: Code of Conduct & Contributing Guidelines By submitting creating this pull request, you agree to follow our [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md): - [x] I agree to follow the [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md). --------- Co-authored-by: Andi Co-authored-by: Paul Schmiedmayer Co-authored-by: Philipp, Zagar --- CONTRIBUTORS.md | 1 + TemplateApplication.xcodeproj/project.pbxproj | 105 +++++++++++++++++- .../xcshareddata/swiftpm/Package.resolved | 40 +++++-- .../Account/AccountSheet.swift | 8 +- .../Contributions/ContributionsList.swift | 55 +++++++++ .../Contributions/Package+LicenseType.swift | 104 +++++++++++++++++ .../Contributions/PackageCell.swift | 74 ++++++++++++ .../Contributions/PackageHelper.swift | 26 +++++ .../Resources/Localizable.xcstrings | 69 ++++++++++++ .../Supporting Files/Info.plist | 16 --- .../ContributionsTest.swift | 40 +++++++ fastlane/Fastfile | 7 +- 12 files changed, 511 insertions(+), 34 deletions(-) create mode 100644 TemplateApplication/Contributions/ContributionsList.swift create mode 100644 TemplateApplication/Contributions/Package+LicenseType.swift create mode 100644 TemplateApplication/Contributions/PackageCell.swift create mode 100644 TemplateApplication/Contributions/PackageHelper.swift create mode 100644 TemplateApplicationUITests/ContributionsTest.swift diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5012e8a..f2f42f0 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -14,3 +14,4 @@ Template Application Contributors * [Paul Schmiedmayer](https://github.com/PSchmiedmayer) * [Andreas Bauer](https://github.com/Supereg) * [Philipp Zagar](https://github.com/philippzagar) +* [Nikolai Madlener](https://github.com/NikolaiMadlener) \ No newline at end of file diff --git a/TemplateApplication.xcodeproj/project.pbxproj b/TemplateApplication.xcodeproj/project.pbxproj index c1607bb..9ef2dfd 100644 --- a/TemplateApplication.xcodeproj/project.pbxproj +++ b/TemplateApplication.xcodeproj/project.pbxproj @@ -61,6 +61,12 @@ 2FE750CD2A87255000723EAE /* SpeziFHIR in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE750CC2A87255000723EAE /* SpeziFHIR */; }; 2FF53D8B2A8725DE00042B76 /* SpeziMockWebService in Frameworks */ = {isa = PBXBuildFile; productRef = 2FF53D8A2A8725DE00042B76 /* SpeziMockWebService */; }; 2FF53D8D2A8729D600042B76 /* TemplateApplicationStandard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FF53D8C2A8729D600042B76 /* TemplateApplicationStandard.swift */; }; + 5661551D2AB8384200209B80 /* SwiftPackageList in Frameworks */ = {isa = PBXBuildFile; productRef = 5661551C2AB8384200209B80 /* SwiftPackageList */; }; + 566155292AB8447C00209B80 /* Package+LicenseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566155282AB8447C00209B80 /* Package+LicenseType.swift */; }; + 5661552E2AB854C000209B80 /* PackageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5661552D2AB854C000209B80 /* PackageHelper.swift */; }; + 5680DD392AB8983D004E6D4A /* PackageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5680DD382AB8983D004E6D4A /* PackageCell.swift */; }; + 5680DD3E2AB8CD84004E6D4A /* ContributionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5680DD3D2AB8CD84004E6D4A /* ContributionsTest.swift */; }; + 56F6F2A02AB441930022FE5A /* ContributionsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56F6F29F2AB441930022FE5A /* ContributionsList.swift */; }; 653A2551283387FE005D4D48 /* TemplateApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A2550283387FE005D4D48 /* TemplateApplication.swift */; }; 653A255528338800005D4D48 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 653A255428338800005D4D48 /* Assets.xcassets */; }; 653A256228338800005D4D48 /* TemplateApplicationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A256128338800005D4D48 /* TemplateApplicationTests.swift */; }; @@ -131,6 +137,11 @@ 2FE5DC5529EDD811004B9AB4 /* SocialSupportQuestionnaire.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SocialSupportQuestionnaire.json; sourceTree = ""; }; 2FE5DCAC29EE6107004B9AB4 /* AccountOnboarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountOnboarding.swift; sourceTree = ""; }; 2FF53D8C2A8729D600042B76 /* TemplateApplicationStandard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemplateApplicationStandard.swift; sourceTree = ""; }; + 566155282AB8447C00209B80 /* Package+LicenseType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Package+LicenseType.swift"; sourceTree = ""; }; + 5661552D2AB854C000209B80 /* PackageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageHelper.swift; sourceTree = ""; }; + 5680DD382AB8983D004E6D4A /* PackageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageCell.swift; sourceTree = ""; }; + 5680DD3D2AB8CD84004E6D4A /* ContributionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContributionsTest.swift; sourceTree = ""; }; + 56F6F29F2AB441930022FE5A /* ContributionsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContributionsList.swift; sourceTree = ""; }; 653A254D283387FE005D4D48 /* TemplateApplication.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TemplateApplication.app; sourceTree = BUILT_PRODUCTS_DIR; }; 653A2550283387FE005D4D48 /* TemplateApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateApplication.swift; sourceTree = ""; }; 653A255428338800005D4D48 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -161,6 +172,7 @@ 2FE5DC6729EDD894004B9AB4 /* SpeziContact in Frameworks */, 2FE5DC8429EDD934004B9AB4 /* SpeziQuestionnaire in Frameworks */, 2FB099B32A875DF100B20952 /* FirebaseFirestoreSwift in Frameworks */, + 5661551D2AB8384200209B80 /* SwiftPackageList in Frameworks */, 2FB099B12A875DF100B20952 /* FirebaseFirestore in Frameworks */, 2FB099B62A875E2B00B20952 /* HealthKitOnFHIR in Frameworks */, 2FE5DC8A29EDD972004B9AB4 /* SpeziLocalStorage in Frameworks */, @@ -285,6 +297,17 @@ path = Helper; sourceTree = ""; }; + 56F6F29E2AB441640022FE5A /* Contributions */ = { + isa = PBXGroup; + children = ( + 56F6F29F2AB441930022FE5A /* ContributionsList.swift */, + 5680DD382AB8983D004E6D4A /* PackageCell.swift */, + 566155282AB8447C00209B80 /* Package+LicenseType.swift */, + 5661552D2AB854C000209B80 /* PackageHelper.swift */, + ); + path = Contributions; + sourceTree = ""; + }; 653A2544283387FE005D4D48 = { isa = PBXGroup; children = ( @@ -319,6 +342,7 @@ 2FE5DC2829EDD398004B9AB4 /* Onboarding */, 2FE5DC3B29EDD7D0004B9AB4 /* Schedule */, 2FE5DC2729EDD38D004B9AB4 /* Contacts */, + 56F6F29E2AB441640022FE5A /* Contributions */, 2F4FC8D529EE69BE00BFFE26 /* MockUpload */, 2FE5DC3C29EDD7DA004B9AB4 /* SharedContext */, 2FE5DC3D29EDD7E4004B9AB4 /* Helper */, @@ -343,6 +367,7 @@ 653A256B28338800005D4D48 /* SchedulerTests.swift */, 2F4E23862989DB360013F3D9 /* ContactsTests.swift */, 2F1B52CD2A4F5CCE003AE151 /* MockUploadTests.swift */, + 5680DD3D2AB8CD84004E6D4A /* ContributionsTest.swift */, ); path = TemplateApplicationUITests; sourceTree = ""; @@ -376,12 +401,14 @@ buildPhases = ( 653A2549283387FE005D4D48 /* Sources */, 653A254A283387FE005D4D48 /* Frameworks */, + 566155202AB8387700209B80 /* Run Script - Generate Swift Package List */, 653A254B283387FE005D4D48 /* Resources */, 2F5B528D29BD237B002020B7 /* ShellScript */, ); buildRules = ( ); dependencies = ( + 566155222AB83CF200209B80 /* PBXTargetDependency */, ); name = TemplateApplication; packageProductDependencies = ( @@ -405,6 +432,7 @@ 2FB099B02A875DF100B20952 /* FirebaseFirestore */, 2FB099B22A875DF100B20952 /* FirebaseFirestoreSwift */, 2FB099B52A875E2B00B20952 /* HealthKitOnFHIR */, + 5661551C2AB8384200209B80 /* SwiftPackageList */, 9739A0C52AD7B5730084BEA5 /* FirebaseStorage */, 97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */, ); @@ -501,6 +529,7 @@ 2FE750CA2A87240100723EAE /* XCRemoteSwiftPackageReference "SpeziMockWebService" */, 2FE750CB2A87255000723EAE /* XCRemoteSwiftPackageReference "SpeziFHIR" */, 2FB099B42A875E2B00B20952 /* XCRemoteSwiftPackageReference "HealthKitOnFHIR" */, + 5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */, ); productRefGroup = 653A254E283387FE005D4D48 /* Products */; projectDirPath = ""; @@ -562,6 +591,25 @@ shellPath = /bin/sh; shellScript = "if [ \"${CONFIGURATION}\" = \"Debug\" ]; then\n export PATH=\"$PATH:/opt/homebrew/bin\"\n if which swiftlint > /dev/null; then\n swiftlint\n else\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\n fi\nfi\n"; }; + 566155202AB8387700209B80 /* Run Script - Generate Swift Package List */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Script - Generate Swift Package List"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if command -v swift-package-list &> /dev/null; then\n OUTPUT_PATH=$SOURCE_ROOT/$TARGETNAME\n swift-package-list generate \"$PROJECT_FILE_PATH\" --output-path \"$OUTPUT_PATH\" --requires-license\nelse\n echo \"warning: swift-package-list not installed\"\nfi\n"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -591,6 +639,9 @@ 2FE5DC5329EDD7FA004B9AB4 /* Bundle+Questionnaire.swift in Sources */, 2FE5DC5129EDD7FA004B9AB4 /* TemplateApplicationTaskContext.swift in Sources */, 97D8B2AB2A769A6E00715F50 /* OnboardingFlow+PreviewSimulator.swift in Sources */, + 56F6F2A02AB441930022FE5A /* ContributionsList.swift in Sources */, + 566155292AB8447C00209B80 /* Package+LicenseType.swift in Sources */, + 5680DD392AB8983D004E6D4A /* PackageCell.swift in Sources */, A92525A92ABC4C7800640379 /* AccountRequiredModifier.swift in Sources */, A92525A72ABC4B5F00640379 /* AccountUpdateModifier.swift in Sources */, 2F5E32BD297E05EA003432F8 /* TemplateAppDelegate.swift in Sources */, @@ -601,6 +652,7 @@ 2F65B44E2A3B8B0600A36932 /* NotificationPermissions.swift in Sources */, A92525AB2ABC4EB200640379 /* AccountRequiredKey.swift in Sources */, 97D8B2A42A7653E600715F50 /* ProcessInfo+PreviewSimulator.swift in Sources */, + 5661552E2AB854C000209B80 /* PackageHelper.swift in Sources */, 27FA29902A388E9B009CAC45 /* ModalView.swift in Sources */, 2FE5DC2629EDD38A004B9AB4 /* Contacts.swift in Sources */, ); @@ -618,6 +670,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5680DD3E2AB8CD84004E6D4A /* ContributionsTest.swift in Sources */, 2F4E23872989DB360013F3D9 /* ContactsTests.swift in Sources */, 2F4E237E2989A2FE0013F3D9 /* OnboardingTests.swift in Sources */, 2F1B52CE2A4F5CCE003AE151 /* MockUploadTests.swift in Sources */, @@ -628,6 +681,10 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 566155222AB83CF200209B80 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = 566155212AB83CF200209B80 /* SwiftPackageListJSONPlugin */; + }; 653A255F28338800005D4D48 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 653A254C283387FE005D4D48 /* TemplateApplication */; @@ -717,6 +774,14 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "TemplateApplication/Supporting Files/Info.plist"; + INFOPLIST_KEY_NSCameraUsageDescription = "This message should never appear. Please adjust this when you start using camera information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; + INFOPLIST_KEY_NSHealthShareUsageDescription = "The Spezi Template Application uses the step count to demonstrate Spezi's integration with HealthKit."; + INFOPLIST_KEY_NSHealthUpdateUsageDescription = "The Spezi Template Application uses the step count to demonstrate Spezi's integration with HealthKit."; + INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "This message should never appear. Please adjust this when you start using location information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "This message should never appear. Please adjust this when you start using location information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "This message should never appear. Please adjust this when you start using microphone information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; + INFOPLIST_KEY_NSMotionUsageDescription = "This message should never appear. Please adjust this when you start using motion information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; + INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "This message should never appear. Please adjust this when you start using speecg information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; @@ -909,6 +974,14 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "TemplateApplication/Supporting Files/Info.plist"; + INFOPLIST_KEY_NSCameraUsageDescription = "This message should never appear. Please adjust this when you start using camera information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; + INFOPLIST_KEY_NSHealthShareUsageDescription = "The Spezi Template Application uses the step count to demonstrate Spezi's integration with HealthKit."; + INFOPLIST_KEY_NSHealthUpdateUsageDescription = "The Spezi Template Application uses the step count to demonstrate Spezi's integration with HealthKit."; + INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "This message should never appear. Please adjust this when you start using location information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "This message should never appear. Please adjust this when you start using location information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "This message should never appear. Please adjust this when you start using microphone information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; + INFOPLIST_KEY_NSMotionUsageDescription = "This message should never appear. Please adjust this when you start using motion information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; + INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "This message should never appear. Please adjust this when you start using speecg information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; @@ -938,15 +1011,22 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "TemplateApplication/Supporting Files/TemplateApplication.entitlements"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 637867499T; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "TemplateApplication/Supporting Files/Info.plist"; + INFOPLIST_KEY_NSCameraUsageDescription = "This message should never appear. Please adjust this when you start using camera information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; + INFOPLIST_KEY_NSHealthShareUsageDescription = "The Spezi Template Application uses the step count to demonstrate Spezi's integration with HealthKit."; + INFOPLIST_KEY_NSHealthUpdateUsageDescription = "The Spezi Template Application uses the step count to demonstrate Spezi's integration with HealthKit."; + INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "This message should never appear. Please adjust this when you start using location information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "This message should never appear. Please adjust this when you start using location information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "This message should never appear. Please adjust this when you start using microphone information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; + INFOPLIST_KEY_NSMotionUsageDescription = "This message should never appear. Please adjust this when you start using motion information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; + INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "This message should never appear. Please adjust this when you start using speecg information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; @@ -960,7 +1040,6 @@ PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.spezi.templateapplication; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Spezi Template Application"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -1211,6 +1290,14 @@ minimumVersion = 0.4.0; }; }; + 5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/FelixHerrmann/swift-package-list"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 3.0.0; + }; + }; 97F466E62A76BBEE005DC9B4 /* XCRemoteSwiftPackageReference "SpeziOnboarding" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/StanfordSpezi/SpeziOnboarding"; @@ -1331,6 +1418,16 @@ package = 2FE750CA2A87240100723EAE /* XCRemoteSwiftPackageReference "SpeziMockWebService" */; productName = SpeziMockWebService; }; + 5661551C2AB8384200209B80 /* SwiftPackageList */ = { + isa = XCSwiftPackageProductDependency; + package = 5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */; + productName = SwiftPackageList; + }; + 566155212AB83CF200209B80 /* SwiftPackageListJSONPlugin */ = { + isa = XCSwiftPackageProductDependency; + package = 5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */; + productName = "plugin:SwiftPackageListJSONPlugin"; + }; 9739A0C52AD7B5730084BEA5 /* FirebaseStorage */ = { isa = XCSwiftPackageProductDependency; package = 2FE5DC9029EDD9C3004B9AB4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; diff --git a/TemplateApplication.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/TemplateApplication.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 272123e..c8dfa8f 100644 --- a/TemplateApplication.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/TemplateApplication.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -75,7 +75,7 @@ { "identity" : "healthkitonfhir", "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordBDHG/HealthKitOnFHIR", + "location" : "https://github.com/StanfordBDHG/HealthKitOnFHIR.git", "state" : { "revision" : "fdf8e4543718a940643598e4bd5e750e9c4c5540", "version" : "0.2.4" @@ -140,8 +140,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/StanfordSpezi/Spezi", "state" : { - "revision" : "7462510badaa156c1e25efd7eabbf5b85ecb0098", - "version" : "0.7.2" + "revision" : "e75ea4d241b6eb611ca4c1c25926097cf324ce37", + "version" : "0.7.3" } }, { @@ -149,8 +149,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/StanfordSpezi/SpeziAccount.git", "state" : { - "revision" : "5937ed6e0baea9f53688c2752acfff23d45335cd", - "version" : "0.5.2" + "revision" : "1eca29373a5c27e7dfe7871be77be306ca626989", + "version" : "0.5.3" } }, { @@ -158,8 +158,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/StanfordSpezi/SpeziContact.git", "state" : { - "revision" : "2d2b8096dcea737723430a3b98ebe3c3a79f6f41", - "version" : "0.4.0" + "revision" : "9dea670bcfea51c2db04ea73b80fda923c2ebaed", + "version" : "0.4.1" } }, { @@ -212,8 +212,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/StanfordSpezi/SpeziQuestionnaire.git", "state" : { - "revision" : "9c4edec01e786447cf37ad272ebbb7b2f12e692a", - "version" : "0.4.2" + "revision" : "849e207da973a089e7a209289217ff3efab68596", + "version" : "0.4.3" } }, { @@ -230,8 +230,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/StanfordSpezi/SpeziStorage.git", "state" : { - "revision" : "739ee1eadc5f9b1b5d2e8752ba077243d42fa79f", - "version" : "0.4.2" + "revision" : "58f79a21291da6d2d2193275486fa3c69b4f31fa", + "version" : "0.4.3" } }, { @@ -252,6 +252,24 @@ "version" : "1.0.5" } }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", + "version" : "1.2.3" + } + }, + { + "identity" : "swift-package-list", + "kind" : "remoteSourceControl", + "location" : "https://github.com/FelixHerrmann/swift-package-list", + "state" : { + "revision" : "374fedd183a1682f3fd5fd0c2e2676d5f3e7c352", + "version" : "3.0.9" + } + }, { "identity" : "swift-protobuf", "kind" : "remoteSourceControl", diff --git a/TemplateApplication/Account/AccountSheet.swift b/TemplateApplication/Account/AccountSheet.swift index 6ec722f..9087dec 100644 --- a/TemplateApplication/Account/AccountSheet.swift +++ b/TemplateApplication/Account/AccountSheet.swift @@ -23,7 +23,13 @@ struct AccountSheet: View { NavigationStack { ZStack { if account.signedIn { - AccountOverview(isEditing: $overviewIsEditing) + AccountOverview(isEditing: $overviewIsEditing) { + NavigationLink { + ContributionsList() + } label: { + Text("LICENSE_INFO_TITLE") + } + } .onDisappear { overviewIsEditing = false } diff --git a/TemplateApplication/Contributions/ContributionsList.swift b/TemplateApplication/Contributions/ContributionsList.swift new file mode 100644 index 0000000..e02a159 --- /dev/null +++ b/TemplateApplication/Contributions/ContributionsList.swift @@ -0,0 +1,55 @@ +// +// This source file is part of the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftPackageList +import SwiftUI + + +struct ContributionsList: View { + var packages: [Package] = PackageHelper.getPackageList() + + var body: some View { + List { + Section(footer: Text("PROJECT_LICENSE_DESCRIPTION")) { + Text("CONTRIBUTIONS_LIST_DESCRIPTION") + } + Section( + header: Text("CONTRIBUTIONS_LIST_HEADER"), + footer: Text("CONTRIBUTIONS_LIST_FOOTER") + ) { + ForEach(packages.sorted(by: { $0.name < $1.name }), id: \.name) { package in + PackageCell(package: package) + } + } + } + .navigationTitle("LICENSE_INFO_TITLE") + .navigationBarTitleDisplayMode(.inline) + } +} + + +#if DEBUG +struct ContributionsList_Previews: PreviewProvider { + static var previews: some View { + let mockPackages = [ + Package( + name: "MockPackage", + version: "1.0", + branch: nil, + revision: "0", + // We use a force unwrap in the preview as we can not recover from an error here + // and the code will never end up in a production environment. + // swiftlint:disable:next force_unwrapping + repositoryURL: URL(string: "github.com")!, + license: "MIT License" + ) + ] + return ContributionsList(packages: mockPackages) + } +} +#endif diff --git a/TemplateApplication/Contributions/Package+LicenseType.swift b/TemplateApplication/Contributions/Package+LicenseType.swift new file mode 100644 index 0000000..f6e5b0e --- /dev/null +++ b/TemplateApplication/Contributions/Package+LicenseType.swift @@ -0,0 +1,104 @@ +// +// This source file is part of the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation +import SwiftPackageList + + +// This section of code is based on the SwiftPackageList package: +// - Original code: https://github.com/FelixHerrmann/swift-package-list/issues/43 +enum LicenseType { + case mit + case apachev2 + case gplv2 + case gplv3 + case bsd2 + case bsd3 + case bsd4 + case zlib + + /// SPDX-License-Identifier for the UI + var spdxIdentifier: String { + switch self { + case .mit: return "MIT" + case .apachev2: return "Apache-2.0" + case .gplv2: return "GPL-2.0" + case .gplv3: return "GPL-3.0" + case .bsd2: return "BSD-2-Clause" + case .bsd3: return "BSD-3-Clause" + case .bsd4: return "BSD-4-Clause" + case .zlib: return "Zlib" + } + } + + /// Initializer that scans the license document for common licenses and versions + init?(license: String) { + let license = license + .replacingOccurrences(of: "\\s+|\\n", with: " ", options: .regularExpression) + + if license.contains(mitText) { + self = .mit + } else if license.contains(apacheText) && license.contains("Version 2.0") { + self = .apachev2 + } else if license.contains(gnuText) && license.contains("Version 2") { + self = .gplv2 + } else if license.contains(gnuText) && license.contains("Version 3") { + self = .gplv3 + } else if license.contains(bsdFourClauseText) { + self = .bsd4 + } else if license.range(of: bsdThreeClausePattern, options: .regularExpression) != nil { + self = .bsd3 + } else if license.contains(bsdTwoClauseText) { + self = .bsd2 + } else if license.range(of: zlibPattern, options: .regularExpression) != nil { + self = .zlib + } else { + return nil + } + } +} + + +// Constants representing typical text and regular expression patterns often found in license files. +// They are used for matching and identifying different types of licenses within text documents. +let mitText = "MIT License" +let apacheText = "Apache License" +let gnuText = "GNU GENERAL PUBLIC LICENSE" +let bsdTwoClauseText = + """ + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met + """ +let bsdThreeClausePattern = + """ + Neither the name of (.+) nor the names of (.+) may be used to endorse or promote products derived from this software \ + without specific prior written permission + """ +let bsdFourClauseText = + """ + All advertising materials mentioning features or use of this software must display the following acknowledgement: \ + this product includes software developed by + """ +let zlibPattern = + """ + The origin of this software must not be misrepresented; you must not claim that you wrote the original software. \ + If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.(.*) \ + Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.(.*) \ + This notice may not be removed or altered from any source distribution. + """ + + +extension Package { + /// Generates the `LicenseType` from a license document of `String` + func getLicenseType(license: String?) -> LicenseType? { + if let license = license { + let licenseType = LicenseType(license: license) + return licenseType + } + return nil + } +} diff --git a/TemplateApplication/Contributions/PackageCell.swift b/TemplateApplication/Contributions/PackageCell.swift new file mode 100644 index 0000000..2f6ab6f --- /dev/null +++ b/TemplateApplication/Contributions/PackageCell.swift @@ -0,0 +1,74 @@ +// +// This source file is part of the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftPackageList +import SwiftUI + + +struct PackageCell: View { + let package: Package + + var body: some View { + HStack { + VStack(alignment: .leading, spacing: 4) { + Text(package.name).font(.headline) + HStack { + Text(getPackageDetails(package: package)) + .font(.caption) + if let licenseType = package.getLicenseType(license: package.license) { + Text(licenseType.spdxIdentifier) + .font(.caption) + .fontWeight(.semibold) + .padding(2) + .background(Color(.systemGray5)) + .cornerRadius(4) + } + } + } + Spacer() + Button(action: { + UIApplication.shared.open(package.repositoryURL) + }) { + Image(systemName: "safari.fill") + .imageScale(.large) + }.buttonStyle(PlainButtonStyle()) + .foregroundColor(.blue) + .accessibilityLabel(Text("Repository Link")) + } + } + + func getPackageDetails(package: Package) -> String { + if let branch = package.branch { + return "Branch: \(branch)" + } else if let version = package.version { + return "Version: \(version)" + } else { + return "Revision: \(package.revision)" + } + } +} + + +#if DEBUG +struct PackageCell_Previews: PreviewProvider { + static var previews: some View { + let mockPackage = Package( + name: "MockPackage", + version: "1.0", + branch: nil, + revision: "0", + // We use a force unwrap in the preview as we can not recover from an error here + // and the code will never end up in a production environment. + // swiftlint:disable:next force_unwrapping + repositoryURL: URL(string: "github.com")!, + license: "MIT License" + ) + return PackageCell(package: mockPackage).previewLayout(.sizeThatFits) + } +} +#endif diff --git a/TemplateApplication/Contributions/PackageHelper.swift b/TemplateApplication/Contributions/PackageHelper.swift new file mode 100644 index 0000000..cb42ab0 --- /dev/null +++ b/TemplateApplication/Contributions/PackageHelper.swift @@ -0,0 +1,26 @@ +// +// This source file is part of the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation +import SwiftPackageList + + +enum PackageHelper { + /// Helper function that calls the corrensponding API of `SwiftPackageList`to fetch the list of packages + static func getPackageList() -> [Package] { + do { + let packages = try packageList() + return packages + } catch PackageListError.noPackageList { + print("There is no package-list file") + } catch { + print(error) + } + return [] + } +} diff --git a/TemplateApplication/Resources/Localizable.xcstrings b/TemplateApplication/Resources/Localizable.xcstrings index e4ef1b3..e93b9fa 100644 --- a/TemplateApplication/Resources/Localizable.xcstrings +++ b/TemplateApplication/Resources/Localizable.xcstrings @@ -107,6 +107,50 @@ } } }, + "CONTRIBUTIONS_LIST_DESCRIPTION" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The following list contains all Swift Package dependencies of the SpeziTemplateApplication." + } + } + } + }, + "CONTRIBUTIONS_LIST_FOOTER" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Please refer to the individual repository links for packages without license labels." + } + } + } + }, + "CONTRIBUTIONS_LIST_HEADER" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Packages" + } + } + } + }, + "CONTRIBUTIONS_LIST_NAVIGATION_TITLE" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "License Information" + } + } + } + }, "HEALTHKIT_PERMISSIONS_BUTTON" : { "localizations" : { "en" : { @@ -269,6 +313,17 @@ } } }, + "LICENSE_INFO_TITLE" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "License Information" + } + } + } + }, "MOCK_WEB_SERVICE_TAB_TITLE" : { "comment" : "MARK: - Mock Upload Data Storage Provider", "localizations" : { @@ -320,6 +375,20 @@ } } } + }, + "PROJECT_LICENSE_DESCRIPTION" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This project is licensed under the MIT License." + } + } + } + }, + "Repository Link" : { + }, "SCHEDULE_LIST_TITLE" : { "localizations" : { diff --git a/TemplateApplication/Supporting Files/Info.plist b/TemplateApplication/Supporting Files/Info.plist index 38a7a8d..47bb94e 100644 --- a/TemplateApplication/Supporting Files/Info.plist +++ b/TemplateApplication/Supporting Files/Info.plist @@ -11,21 +11,5 @@ UISceneConfigurations - NSHealthShareUsageDescription - The Spezi Template Application uses the step count to demonstrate Spezi's integration with HealthKit. - NSHealthUpdateUsageDescription - The Spezi Template Application uses the step count to demonstrate Spezi's integration with HealthKit. - NSLocationWhenInUseUsageDescription - This message should never appear. Please adjust this when you start using location information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect. - NSLocationAlwaysAndWhenInUseUsageDescription - This message should never appear. Please adjust this when you start using location information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect. - NSSpeechRecognitionUsageDescription - This message should never appear. Please adjust this when you start using speecg information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect. - NSMotionUsageDescription - This message should never appear. Please adjust this when you start using motion information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect. - NSCameraUsageDescription - This message should never appear. Please adjust this when you start using camera information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect. - NSMicrophoneUsageDescription - This message should never appear. Please adjust this when you start using microphone information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect. diff --git a/TemplateApplicationUITests/ContributionsTest.swift b/TemplateApplicationUITests/ContributionsTest.swift new file mode 100644 index 0000000..bc9f69f --- /dev/null +++ b/TemplateApplicationUITests/ContributionsTest.swift @@ -0,0 +1,40 @@ +// +// This source file is part of the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import XCTest + + +final class ContributionsTest: XCTestCase { + override func setUpWithError() throws { + try super.setUpWithError() + + try disablePasswordAutofill() + + continueAfterFailure = false + + let app = XCUIApplication() + app.launchArguments = ["--showOnboarding"] + app.deleteAndLaunch(withSpringboardAppName: "TemplateApplication") + } + + func testLicenseInformationPage() throws { + let app = XCUIApplication() + // complete onboarding so user is logged in + try app.conductOnboardingIfNeeded() + + + XCTAssertTrue(app.buttons["Your Account"].waitForExistence(timeout: 2)) + app.buttons["Your Account"].tap() + + XCTAssertTrue(app.buttons["License Information"].waitForExistence(timeout: 2)) + app.buttons["License Information"].tap() + // Test if the sheet opens by checking if the title of the sheet is present + XCTAssertTrue(app.staticTexts["This project is licensed under the MIT License."].waitForExistence(timeout: 2)) + XCTAssertTrue(app.buttons["Repository Link"].waitForExistence(timeout: 2)) + } +} diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a836bd8..7aeeac8 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -27,7 +27,8 @@ platform :ios do concurrent_workers: 1, max_concurrent_simulators: 1, result_bundle: true, - output_directory: "." + output_directory: ".", + xcargs: "-skipPackagePluginValidation" ) end @@ -36,7 +37,8 @@ platform :ios do build_app( skip_archive: true, skip_codesigning: true, - derived_data_path: ".derivedData" + derived_data_path: ".derivedData", + xcargs: "-skipPackagePluginValidation" ) end @@ -44,6 +46,7 @@ platform :ios do lane :build do build_app( derived_data_path: ".derivedData", + xcargs: "-skipPackagePluginValidation", export_options: { provisioningProfiles: { "edu.stanford.spezi.templateapplication" => "Spezi Template Application"